Merge "Update fragment dependency" into androidx-main
diff --git a/.github/workflows/presubmit.yml b/.github/workflows/presubmit.yml
index 8858e9e..09b9ddb 100644
--- a/.github/workflows/presubmit.yml
+++ b/.github/workflows/presubmit.yml
@@ -560,15 +560,6 @@
wrapper-directory: ${{ env.project-root }}/gradle/wrapper
wrapper-cache-enabled: true
-
- - name: "upload build artifacts"
- continue-on-error: true
- if: always()
- uses: actions/upload-artifact@v2
- with:
- name: artifacts_${{ env.artifact-id }}
- path: ~/dist
-
- name: Set up Cloud SDK
uses: google-github-actions/setup-gcloud@master
with:
@@ -588,6 +579,13 @@
wrapper-directory: ${{ env.project-root }}/gradle/wrapper
wrapper-cache-enabled: true
+ - name: "upload build artifacts"
+ continue-on-error: true
+ if: always()
+ uses: actions/upload-artifact@v2
+ with:
+ name: artifacts_${{ env.artifact-id }}
+ path: ~/dist
- name: "Report job status"
id: output-status
if: always()
diff --git a/buildSrc/src/main/kotlin/androidx/build/LibraryVersions.kt b/buildSrc/src/main/kotlin/androidx/build/LibraryVersions.kt
index a649d44..8dd795e 100644
--- a/buildSrc/src/main/kotlin/androidx/build/LibraryVersions.kt
+++ b/buildSrc/src/main/kotlin/androidx/build/LibraryVersions.kt
@@ -96,7 +96,7 @@
val PREFERENCE = Version("1.2.0-alpha01")
val RECOMMENDATION = Version("1.1.0-alpha01")
val RECYCLERVIEW = Version("1.3.0-alpha01")
- val RECYCLERVIEW_SELECTION = Version("2.0.0-alpha01")
+ val RECYCLERVIEW_SELECTION = Version("1.2.0-alpha01")
val REMOTECALLBACK = Version("1.0.0-alpha02")
val RESOURCEINSPECTION = Version("1.0.0-alpha01")
val ROOM = Version("2.4.0-alpha02")
diff --git a/buildSrc/src/main/kotlin/androidx/build/ftl/GCloudCLIWrapper.kt b/buildSrc/src/main/kotlin/androidx/build/ftl/GCloudCLIWrapper.kt
index 80b80c8..385e271 100644
--- a/buildSrc/src/main/kotlin/androidx/build/ftl/GCloudCLIWrapper.kt
+++ b/buildSrc/src/main/kotlin/androidx/build/ftl/GCloudCLIWrapper.kt
@@ -16,6 +16,7 @@
package androidx.build.ftl
+import androidx.build.ftl.GCloudCLIWrapper.RunTestParameters.Companion.TEST_OUTPUT_FILE_NAME
import com.google.gson.Gson
import com.google.gson.annotations.SerializedName
import com.google.gson.reflect.TypeToken
@@ -24,6 +25,7 @@
import java.io.ByteArrayOutputStream
import java.io.File
import java.util.Locale
+import java.util.UUID
/**
* Wrapper around GCloud CLI.
@@ -44,53 +46,102 @@
/**
* Path to the gcloud executable, derived from `which gcloud` call.
*/
- private val executable: String by lazy {
- val output = ByteArrayOutputStream()
- val result = execOperations.exec {
- it.commandLine("which", "gcloud")
- it.standardOutput = output
- it.isIgnoreExitValue = true
- }
- if (result.exitValue != 0) {
- throw GradleException(
- """
- Unable to find gcloud CLI executable.
- `which gcloud` returned exit code ${result.exitValue}.
- Make sure gcloud CLI is installed, authenticated and is part of your PATH.
- See https://cloud.google.com/sdk/gcloud for installation instructions.
- """.trimIndent()
- )
- }
- output.toString(Charsets.UTF_8).trim()
+ private val gcloud: String by lazy {
+ findExecutable("gcloud")
+ }
+
+ /**
+ * Path to the gsutil executable, derived from `which gsutil` call.
+ */
+ private val gsutil: String by lazy {
+ findExecutable("gsutil")
}
private inline fun <reified T> executeGcloud(
vararg params: String
): T {
val output = ByteArrayOutputStream()
- execOperations.exec {
- it.executable = executable
+ val errorOutput = ByteArrayOutputStream()
+ val execResult = execOperations.exec {
+ it.executable = gcloud
it.args = params.toList() + "--format=json"
it.standardOutput = output
+ it.errorOutput = errorOutput
+ it.isIgnoreExitValue = true
}
+ if (execResult.exitValue != 0) {
+ System.err.println("GCloud command failed: ${errorOutput.toString(Charsets.UTF_8)}")
+ }
+ // still try to parse the because when it fails (e.g. test failure), it returns a non-0
+ // exit code but still prints the output. We are interested in the output.
val commandOutput = output.toString(Charsets.UTF_8)
return gson.parse(commandOutput)
}
+ private fun execGsUtil(
+ vararg params: String
+ ): String {
+ val output = ByteArrayOutputStream()
+ execOperations.exec {
+ it.executable = gsutil
+ it.args = params.toList()
+ it.standardOutput = output
+ }
+ return output.toString(Charsets.UTF_8)
+ }
+
/**
* https://cloud.google.com/sdk/gcloud/reference/firebase/test/android/run
*/
fun runTest(
- testedApk: File,
- testApk: File
+ params: RunTestParameters
): List<TestResult> {
- return executeGcloud(
+ val testResults = executeGcloud<List<TestResult>>(
"firebase", "test", "android", "run",
"--type", "instrumentation",
- "--test", testApk.canonicalPath,
- "--app", testedApk.canonicalPath,
- "--num-flaky-test-attempts", "3",
+ "--test", params.testApk.canonicalPath,
+ "--app", params.testedApk.canonicalPath,
+ "--num-flaky-test-attempts", "2",
+ "--results-bucket=${params.bucketName}",
+ "--results-dir=${params.resultsBucketDir}",
+ "--results-history-name=${params.projectPath}"
)
+ // copy the test results from the bucket to the build directory
+ execGsUtil(
+ "cp", "-r", params.cloudBucketPath() + "/*", params.resultsLocalDir.canonicalPath
+ )
+ // finally, write the command response into the directory as well
+ val testResultOutput = params.resultsLocalDir.resolve(TEST_OUTPUT_FILE_NAME)
+ testResultOutput.bufferedWriter(Charsets.UTF_8).use {
+ gson.toJson(
+ testResults,
+ it
+ )
+ }
+ return testResults
+ }
+
+ /**
+ * find the given executable's path in the PATH via `which` command.
+ */
+ private fun findExecutable(name: String): String {
+ val output = ByteArrayOutputStream()
+ val result = execOperations.exec {
+ it.commandLine("which", name)
+ it.standardOutput = output
+ it.isIgnoreExitValue = true
+ }
+ if (result.exitValue != 0) {
+ throw GradleException(
+ """
+ Unable to find $name CLI executable.
+ `which $name` returned exit code ${result.exitValue}.
+ Make sure gcloud CLI is installed, authenticated and is part of your PATH.
+ See https://cloud.google.com/sdk/gcloud for installation instructions.
+ """.trimIndent()
+ )
+ }
+ return output.toString(Charsets.UTF_8).trim()
}
/**
@@ -110,6 +161,95 @@
private val SUCCESS_OUTCOMES = listOf("passed", "flaky")
}
}
+
+ /**
+ * Parameters for invoking a test on the Firebase Test Lab
+ */
+ internal data class RunTestParameters(
+ /**
+ * The project path for which we are executing the tests for.
+ */
+ val projectPath: String,
+ /**
+ * The tested APK file
+ */
+ val testedApk: File,
+ /**
+ * The test APK file which includes the instrumentation tests
+ */
+ val testApk: File,
+ /**
+ * The name of the GS bucket to save the results
+ */
+ val bucketName: String = DEFAULT_BUCKET_NAME,
+ /**
+ * The GS Bucket directory where the results will be saved
+ */
+ val resultsBucketDir: String = buildRelativeResultDirPath(projectPath),
+ /**
+ * The local directory where we will download the test results
+ */
+ val resultsLocalDir: File,
+ ) {
+
+ /**
+ * Returns the path to the Google Cloud bucket where the test run results are saved
+ */
+ fun cloudBucketPath(): String {
+ return "gs://$bucketName/$resultsBucketDir"
+ }
+
+ companion object {
+ const val DEFAULT_BUCKET_NAME = "androidx-ftl-test-results"
+
+ /**
+ * The file into which the result of the gcloud command will be written.
+ */
+ const val TEST_OUTPUT_FILE_NAME = "testResults.json"
+
+ /**
+ * Generates a relative path for test results.
+ *
+ * If run on Github Actions CI, this method will use the environment variables to
+ * create a unique path for the action.
+ * If run locally, this will create a random UUID for the directory.
+ */
+ private fun buildRelativeResultDirPath(
+ projectPath: String
+ ): String {
+ // github action env variables:
+ // https://docs.github.com/en/actions/reference/environment-variables
+ val inGithubActions = System.getenv().containsKey("GITHUB_ACTIONS")
+ val pathValues = if (inGithubActions) {
+ val workflowName = requireEnvValue("GITHUB_WORKFLOW")
+ val runNumber = requireEnvValue("GITHUB_RUN_NUMBER")
+ val runId = requireEnvValue("GITHUB_RUN_ID")
+ val ref = System.getenv("GITHUB_REF")
+ listOfNotNull(
+ "github",
+ projectPath,
+ ref,
+ workflowName,
+ runNumber,
+ runId,
+ )
+ } else {
+ listOf(
+ "local",
+ projectPath,
+ UUID.randomUUID().toString()
+ )
+ }
+ return pathValues.joinToString("/")
+ }
+
+ private fun requireEnvValue(name: String): String {
+ return System.getenv(name) ?: throw GradleException(
+ "Cannot find required environment variable: $name"
+ )
+ }
+ }
+ }
}
private inline fun <reified T> Gson.parse(
diff --git a/buildSrc/src/main/kotlin/androidx/build/ftl/RunTestOnFTLTask.kt b/buildSrc/src/main/kotlin/androidx/build/ftl/RunTestOnFTLTask.kt
index 0d4f9a3..8ec207d 100644
--- a/buildSrc/src/main/kotlin/androidx/build/ftl/RunTestOnFTLTask.kt
+++ b/buildSrc/src/main/kotlin/androidx/build/ftl/RunTestOnFTLTask.kt
@@ -16,16 +16,21 @@
package androidx.build.ftl
+import androidx.build.getDistributionDirectory
+import androidx.build.getSupportRootFolder
import com.android.build.gradle.api.ApkVariant
import com.android.build.gradle.api.ApkVariantOutput
import com.android.build.gradle.api.TestVariant
-import com.google.gson.Gson
import org.gradle.api.DefaultTask
import org.gradle.api.GradleException
import org.gradle.api.Project
+import org.gradle.api.file.DirectoryProperty
import org.gradle.api.file.RegularFileProperty
+import org.gradle.api.provider.Property
+import org.gradle.api.tasks.CacheableTask
+import org.gradle.api.tasks.Copy
import org.gradle.api.tasks.InputFile
-import org.gradle.api.tasks.OutputFile
+import org.gradle.api.tasks.OutputDirectory
import org.gradle.api.tasks.PathSensitive
import org.gradle.api.tasks.PathSensitivity
import org.gradle.api.tasks.TaskAction
@@ -45,6 +50,7 @@
* Due to the limitations of FTL, this task only support application instrumentation tests for now.
*/
@Suppress("UnstableApiUsage") // for gradle property APIs
+@CacheableTask
abstract class RunTestOnFTLTask @Inject constructor(
private val workerExecutor: WorkerExecutor
) : DefaultTask() {
@@ -63,8 +69,8 @@
/**
* Output file to write the results
*/
- @get:OutputFile
- abstract val testResults: RegularFileProperty
+ @get:OutputDirectory
+ abstract val testResults: DirectoryProperty
@TaskAction
fun executeTest() {
@@ -74,35 +80,37 @@
it.testApk.set(testApk)
it.testedApk.set(testedApk)
it.testResults.set(testResults)
+ it.projectPath.set(project.relativeResultPath())
}
}
interface RunFTLTestParams : WorkParameters {
+ val projectPath: Property<String>
val testApk: RegularFileProperty
val testedApk: RegularFileProperty
- val testResults: RegularFileProperty
+ val testResults: DirectoryProperty
}
abstract class RunFTLTestWorkAction @Inject constructor(
private val execOperations: ExecOperations
) : WorkAction<RunFTLTestParams> {
override fun execute() {
+ val localTestResultDir = parameters.testResults.asFile.get()
+ localTestResultDir.apply {
+ deleteRecursively()
+ mkdirs()
+ }
val testApk = parameters.testApk.asFile.get()
val testedApk = parameters.testedApk.asFile.get()
val gcloud = GCloudCLIWrapper(execOperations)
- val result = gcloud.runTest(
+ val params = GCloudCLIWrapper.RunTestParameters(
testedApk = testedApk,
- testApk = testApk
+ testApk = testApk,
+ projectPath = parameters.projectPath.get(),
+ resultsLocalDir = localTestResultDir
+
)
- val outFile = parameters.testResults.asFile.get()
- outFile.parentFile.mkdirs()
- val gson = Gson()
- outFile.bufferedWriter(Charsets.UTF_8).use {
- gson.toJson(
- result,
- it
- )
- }
+ val result = gcloud.runTest(params)
val failed = result.filterNot {
it.passed
}
@@ -114,7 +122,6 @@
companion object {
private const val TASK_SUFFIX = "OnFirebaseTestLab"
- private const val TEST_OUTPUT_FILE_NAME = "testResults.json"
/**
* Creates an FTL test runner task and returns it.
@@ -128,16 +135,26 @@
val testedVariant = testVariant.testedVariant as? ApkVariant
?: return null
val taskName = testVariant.name + TASK_SUFFIX
+ val testResultDir = project.layout.buildDirectory.dir(
+ "ftl-results"
+ )
+ // create task to copy results into dist directory
+ val copyToDistTask = project.tasks.register(
+ "copyResultsOf${taskName}ToDist",
+ Copy::class.java
+ ) {
+ it.description = "Copy test results from $taskName into DIST folder"
+ it.group = "build"
+ it.from(testResultDir)
+ it.into(
+ project.getDistributionDirectory()
+ .resolve("ftl-results/${project.relativeResultPath()}/$taskName")
+ )
+ }
return project.tasks.register(taskName, RunTestOnFTLTask::class.java) { task ->
task.description = "Run ${testVariant.name} tests on Firebase Test Lab"
task.group = "Verification"
- task.testResults.set(
- project.layout.buildDirectory.dir(
- "ftl-results"
- ).map {
- it.file(TEST_OUTPUT_FILE_NAME)
- }
- )
+ task.testResults.set(testResultDir)
task.dependsOn(testVariant.packageApplicationProvider)
task.dependsOn(testedVariant.packageApplicationProvider)
@@ -153,7 +170,16 @@
.firstOrNull()
?.outputFile
)
+ task.finalizedBy(copyToDistTask)
}
}
}
-}
\ No newline at end of file
+}
+
+/**
+ * Returns the relative path of the project wrt the support root. This path is used for both
+ * local dist path and cloud bucket paths.
+ */
+private fun Project.relativeResultPath() = projectDir.relativeTo(
+ project.getSupportRootFolder()
+).path
\ No newline at end of file
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/LazyList.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/LazyList.kt
index 737d89a..ec34900 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/LazyList.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/LazyList.kt
@@ -33,6 +33,9 @@
import androidx.compose.ui.draw.clipToBounds
import androidx.compose.ui.layout.SubcomposeLayout
import androidx.compose.ui.platform.LocalLayoutDirection
+import androidx.compose.ui.semantics.CollectionInfo
+import androidx.compose.ui.semantics.collectionInfo
+import androidx.compose.ui.semantics.semantics
import androidx.compose.ui.unit.Density
import androidx.compose.ui.unit.LayoutDirection
@@ -85,6 +88,12 @@
.clipToBounds()
.padding(contentPadding)
.then(state.remeasurementModifier)
+ .semantics {
+ collectionInfo = CollectionInfo(
+ rowCount = if (isVertical) -1 else 1,
+ columnCount = if (isVertical) 1 else -1
+ )
+ }
) { constraints ->
constraints.assertNotNestingScrollableContainers(isVertical)
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/selection/SelectableGroup.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/selection/SelectableGroup.kt
index e433162..1ff8402 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/selection/SelectableGroup.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/selection/SelectableGroup.kt
@@ -26,6 +26,6 @@
*
* @see selectableGroup
*/
-fun Modifier.selectableGroup() = this.semantics(true) {
+fun Modifier.selectableGroup() = this.semantics {
selectableGroup()
}
\ No newline at end of file
diff --git a/compose/integration-tests/demos/src/androidTest/java/androidx/compose/integration/demos/test/DemoTest.kt b/compose/integration-tests/demos/src/androidTest/java/androidx/compose/integration/demos/test/DemoTest.kt
index b6b215f..e90c1cf 100644
--- a/compose/integration-tests/demos/src/androidTest/java/androidx/compose/integration/demos/test/DemoTest.kt
+++ b/compose/integration-tests/demos/src/androidTest/java/androidx/compose/integration/demos/test/DemoTest.kt
@@ -182,6 +182,10 @@
Espresso.pressBack()
rule.waitForIdle()
}
+
+ clearFocusFromDemo()
+ rule.waitForIdle()
+
Espresso.pressBack()
rule.waitForIdle()
@@ -212,6 +216,23 @@
private fun SemanticsNodeInteractionCollection.isNotEmpty(): Boolean {
return fetchSemanticsNodes(atLeastOneRootRequired = false).isNotEmpty()
}
+
+ private fun clearFocusFromDemo() {
+ with(rule.activity) {
+ if (hostView.hasFocus()) {
+ if (hostView.isFocused) {
+ // One of the Compose components has focus.
+ focusManager.clearFocus(forcedClear = true)
+ } else {
+ // A child view has focus. (View interop scenario).
+ // We could also use hostViewGroup.focusedChild?.clearFocus(), but the
+ // interop views might end up being focused if one of them is marked as
+ // focusedByDefault. So we clear focus by requesting focus on the owner.
+ rule.runOnUiThread { hostView.requestFocus() }
+ }
+ }
+ }
+ }
}
private val AllButIgnoredDemos =
diff --git a/compose/integration-tests/demos/src/main/java/androidx/compose/integration/demos/DemoActivity.kt b/compose/integration-tests/demos/src/main/java/androidx/compose/integration/demos/DemoActivity.kt
index d858192..8f59333 100644
--- a/compose/integration-tests/demos/src/main/java/androidx/compose/integration/demos/DemoActivity.kt
+++ b/compose/integration-tests/demos/src/main/java/androidx/compose/integration/demos/DemoActivity.kt
@@ -21,37 +21,43 @@
import android.content.Intent
import android.content.SharedPreferences
import android.os.Bundle
+import android.view.View
import android.view.Window
import androidx.activity.ComponentActivity
import androidx.activity.OnBackPressedCallback
import androidx.activity.OnBackPressedDispatcher
-import androidx.compose.runtime.Composable
-import androidx.compose.runtime.getValue
-import androidx.compose.runtime.mutableStateOf
-import androidx.compose.runtime.remember
-import androidx.compose.runtime.setValue
-import androidx.lifecycle.Lifecycle
-import androidx.lifecycle.LifecycleEventObserver
-import androidx.preference.PreferenceManager
import androidx.compose.integration.demos.common.ActivityDemo
import androidx.compose.integration.demos.common.Demo
import androidx.compose.integration.demos.common.DemoCategory
-import androidx.compose.ui.graphics.toArgb
import androidx.compose.material.Colors
import androidx.compose.material.MaterialTheme
import androidx.compose.material.darkColors
import androidx.compose.material.lightColors
+import androidx.compose.runtime.Composable
import androidx.compose.runtime.SideEffect
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
import androidx.compose.runtime.saveable.Saver
import androidx.compose.runtime.saveable.listSaver
import androidx.compose.runtime.saveable.rememberSaveable
+import androidx.compose.runtime.setValue
+import androidx.compose.ui.focus.FocusManager
import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.graphics.toArgb
import androidx.compose.ui.platform.ComposeView
+import androidx.compose.ui.platform.LocalFocusManager
+import androidx.compose.ui.platform.LocalView
+import androidx.lifecycle.Lifecycle
+import androidx.lifecycle.LifecycleEventObserver
+import androidx.preference.PreferenceManager
/**
* Main [Activity] containing all Compose related demos.
*/
class DemoActivity : ComponentActivity() {
+ lateinit var hostView: View
+ lateinit var focusManager: FocusManager
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
@@ -59,6 +65,8 @@
ComposeView(this).also {
setContentView(it)
}.setContent {
+ hostView = LocalView.current
+ focusManager = LocalFocusManager.current
val activityStarter = fun(demo: ActivityDemo<*>) {
startActivity(Intent(this, demo.activityClass.java))
}
diff --git a/compose/material/material/integration-tests/material-demos/src/main/java/androidx/compose/material/demos/BottomNavigationDemo.kt b/compose/material/material/integration-tests/material-demos/src/main/java/androidx/compose/material/demos/BottomNavigationDemo.kt
index 50d8776..02ff422 100644
--- a/compose/material/material/integration-tests/material-demos/src/main/java/androidx/compose/material/demos/BottomNavigationDemo.kt
+++ b/compose/material/material/integration-tests/material-demos/src/main/java/androidx/compose/material/demos/BottomNavigationDemo.kt
@@ -43,42 +43,44 @@
fun BottomNavigationDemo() {
var alwaysShowLabels by remember { mutableStateOf(false) }
Column(
- modifier = Modifier.fillMaxHeight().selectableGroup(),
+ modifier = Modifier.fillMaxHeight(),
verticalArrangement = Arrangement.Bottom
) {
- Row(
- modifier = Modifier
- .fillMaxWidth()
- .height(56.dp)
- .selectable(
+ Column(Modifier.selectableGroup()) {
+ Row(
+ modifier = Modifier
+ .fillMaxWidth()
+ .height(56.dp)
+ .selectable(
+ selected = !alwaysShowLabels,
+ onClick = { alwaysShowLabels = false }
+ ),
+ verticalAlignment = Alignment.CenterVertically
+ ) {
+ RadioButton(
selected = !alwaysShowLabels,
- onClick = { alwaysShowLabels = false }
- ),
- verticalAlignment = Alignment.CenterVertically
- ) {
- RadioButton(
- selected = !alwaysShowLabels,
- onClick = null
- )
- Spacer(Modifier.requiredWidth(16.dp))
- Text("Only show labels when selected")
- }
- Row(
- modifier = Modifier
- .fillMaxWidth()
- .height(56.dp)
- .selectable(
+ onClick = null
+ )
+ Spacer(Modifier.requiredWidth(16.dp))
+ Text("Only show labels when selected")
+ }
+ Row(
+ modifier = Modifier
+ .fillMaxWidth()
+ .height(56.dp)
+ .selectable(
+ selected = alwaysShowLabels,
+ onClick = { alwaysShowLabels = true }
+ ),
+ verticalAlignment = Alignment.CenterVertically
+ ) {
+ RadioButton(
selected = alwaysShowLabels,
- onClick = { alwaysShowLabels = true }
- ),
- verticalAlignment = Alignment.CenterVertically
- ) {
- RadioButton(
- selected = alwaysShowLabels,
- onClick = null
- )
- Spacer(Modifier.requiredWidth(16.dp))
- Text("Always show labels")
+ onClick = null
+ )
+ Spacer(Modifier.requiredWidth(16.dp))
+ Text("Always show labels")
+ }
}
Spacer(Modifier.height(50.dp))
diff --git a/compose/material/material/src/commonMain/kotlin/androidx/compose/material/TabRow.kt b/compose/material/material/src/commonMain/kotlin/androidx/compose/material/TabRow.kt
index 604dd36..eda5587 100644
--- a/compose/material/material/src/commonMain/kotlin/androidx/compose/material/TabRow.kt
+++ b/compose/material/material/src/commonMain/kotlin/androidx/compose/material/TabRow.kt
@@ -221,7 +221,7 @@
tabs: @Composable () -> Unit
) {
Surface(
- modifier = modifier.selectableGroup(),
+ modifier = modifier,
color = backgroundColor,
contentColor = contentColor
) {
@@ -237,6 +237,7 @@
Modifier.fillMaxWidth()
.wrapContentSize(align = Alignment.CenterStart)
.horizontalScroll(scrollState)
+ .selectableGroup()
.clipToBounds()
) { constraints ->
val minTabWidth = ScrollableTabRowMinimumTabWidth.roundToPx()
diff --git a/compose/ui/ui/api/1.0.0-beta06.txt b/compose/ui/ui/api/1.0.0-beta06.txt
index b6757ba..40f9f97 100644
--- a/compose/ui/ui/api/1.0.0-beta06.txt
+++ b/compose/ui/ui/api/1.0.0-beta06.txt
@@ -2058,6 +2058,13 @@
}
+package androidx.compose.ui.platform.accessibility {
+
+ public final class CollectionInfoKt {
+ }
+
+}
+
package androidx.compose.ui.platform.actionmodecallback {
public final class TextActionModeCallback_androidKt {
@@ -2120,6 +2127,26 @@
property public final String? label;
}
+ public final class CollectionInfo {
+ ctor public CollectionInfo(int rowCount, int columnCount);
+ method public int getColumnCount();
+ method public int getRowCount();
+ property public final int columnCount;
+ property public final int rowCount;
+ }
+
+ public final class CollectionItemInfo {
+ ctor public CollectionItemInfo(int rowIndex, int rowSpan, int columnIndex, int columnSpan);
+ method public int getColumnIndex();
+ method public int getColumnSpan();
+ method public int getRowIndex();
+ method public int getRowSpan();
+ property public final int columnIndex;
+ property public final int columnSpan;
+ property public final int rowIndex;
+ property public final int rowSpan;
+ }
+
public final class CustomAccessibilityAction {
ctor public CustomAccessibilityAction(String label, kotlin.jvm.functions.Function0<java.lang.Boolean> action);
method public kotlin.jvm.functions.Function0<java.lang.Boolean> getAction();
@@ -2280,6 +2307,8 @@
}
public final class SemanticsProperties {
+ method public androidx.compose.ui.semantics.SemanticsPropertyKey<androidx.compose.ui.semantics.CollectionInfo> getCollectionInfo();
+ method public androidx.compose.ui.semantics.SemanticsPropertyKey<androidx.compose.ui.semantics.CollectionItemInfo> getCollectionItemInfo();
method public androidx.compose.ui.semantics.SemanticsPropertyKey<java.lang.String> getContentDescription();
method public androidx.compose.ui.semantics.SemanticsPropertyKey<kotlin.Unit> getDisabled();
method public androidx.compose.ui.semantics.SemanticsPropertyKey<androidx.compose.ui.text.AnnotatedString> getEditableText();
@@ -2305,6 +2334,8 @@
method public androidx.compose.ui.semantics.SemanticsPropertyKey<androidx.compose.ui.text.TextRange> getTextSelectionRange();
method public androidx.compose.ui.semantics.SemanticsPropertyKey<androidx.compose.ui.state.ToggleableState> getToggleableState();
method public androidx.compose.ui.semantics.SemanticsPropertyKey<androidx.compose.ui.semantics.ScrollAxisRange> getVerticalScrollAxisRange();
+ property public final androidx.compose.ui.semantics.SemanticsPropertyKey<androidx.compose.ui.semantics.CollectionInfo> CollectionInfo;
+ property public final androidx.compose.ui.semantics.SemanticsPropertyKey<androidx.compose.ui.semantics.CollectionItemInfo> CollectionItemInfo;
property public final androidx.compose.ui.semantics.SemanticsPropertyKey<java.lang.String> ContentDescription;
property public final androidx.compose.ui.semantics.SemanticsPropertyKey<kotlin.Unit> Disabled;
property public final androidx.compose.ui.semantics.SemanticsPropertyKey<androidx.compose.ui.text.AnnotatedString> EditableText;
@@ -2342,6 +2373,8 @@
method public static void dismiss(androidx.compose.ui.semantics.SemanticsPropertyReceiver, optional String? label, kotlin.jvm.functions.Function0<java.lang.Boolean>? action);
method public static void error(androidx.compose.ui.semantics.SemanticsPropertyReceiver, String description);
method public static void expand(androidx.compose.ui.semantics.SemanticsPropertyReceiver, optional String? label, kotlin.jvm.functions.Function0<java.lang.Boolean>? action);
+ method public static androidx.compose.ui.semantics.CollectionInfo getCollectionInfo(androidx.compose.ui.semantics.SemanticsPropertyReceiver);
+ method public static androidx.compose.ui.semantics.CollectionItemInfo getCollectionItemInfo(androidx.compose.ui.semantics.SemanticsPropertyReceiver);
method public static String getContentDescription(androidx.compose.ui.semantics.SemanticsPropertyReceiver);
method public static java.util.List<androidx.compose.ui.semantics.CustomAccessibilityAction> getCustomActions(androidx.compose.ui.semantics.SemanticsPropertyReceiver);
method public static androidx.compose.ui.text.AnnotatedString getEditableText(androidx.compose.ui.semantics.SemanticsPropertyReceiver);
@@ -2370,6 +2403,8 @@
method public static void scrollBy(androidx.compose.ui.semantics.SemanticsPropertyReceiver, optional String? label, kotlin.jvm.functions.Function2<? super java.lang.Float,? super java.lang.Float,java.lang.Boolean>? action);
method public static void scrollToIndex(androidx.compose.ui.semantics.SemanticsPropertyReceiver, optional String? label, kotlin.jvm.functions.Function1<? super java.lang.Integer,java.lang.Boolean> action);
method public static void selectableGroup(androidx.compose.ui.semantics.SemanticsPropertyReceiver);
+ method public static void setCollectionInfo(androidx.compose.ui.semantics.SemanticsPropertyReceiver, androidx.compose.ui.semantics.CollectionInfo p);
+ method public static void setCollectionItemInfo(androidx.compose.ui.semantics.SemanticsPropertyReceiver, androidx.compose.ui.semantics.CollectionItemInfo p);
method public static void setContentDescription(androidx.compose.ui.semantics.SemanticsPropertyReceiver, String p);
method public static void setCustomActions(androidx.compose.ui.semantics.SemanticsPropertyReceiver, java.util.List<androidx.compose.ui.semantics.CustomAccessibilityAction> p);
method public static void setEditableText(androidx.compose.ui.semantics.SemanticsPropertyReceiver, androidx.compose.ui.text.AnnotatedString p);
diff --git a/compose/ui/ui/api/current.txt b/compose/ui/ui/api/current.txt
index b6757ba..40f9f97 100644
--- a/compose/ui/ui/api/current.txt
+++ b/compose/ui/ui/api/current.txt
@@ -2058,6 +2058,13 @@
}
+package androidx.compose.ui.platform.accessibility {
+
+ public final class CollectionInfoKt {
+ }
+
+}
+
package androidx.compose.ui.platform.actionmodecallback {
public final class TextActionModeCallback_androidKt {
@@ -2120,6 +2127,26 @@
property public final String? label;
}
+ public final class CollectionInfo {
+ ctor public CollectionInfo(int rowCount, int columnCount);
+ method public int getColumnCount();
+ method public int getRowCount();
+ property public final int columnCount;
+ property public final int rowCount;
+ }
+
+ public final class CollectionItemInfo {
+ ctor public CollectionItemInfo(int rowIndex, int rowSpan, int columnIndex, int columnSpan);
+ method public int getColumnIndex();
+ method public int getColumnSpan();
+ method public int getRowIndex();
+ method public int getRowSpan();
+ property public final int columnIndex;
+ property public final int columnSpan;
+ property public final int rowIndex;
+ property public final int rowSpan;
+ }
+
public final class CustomAccessibilityAction {
ctor public CustomAccessibilityAction(String label, kotlin.jvm.functions.Function0<java.lang.Boolean> action);
method public kotlin.jvm.functions.Function0<java.lang.Boolean> getAction();
@@ -2280,6 +2307,8 @@
}
public final class SemanticsProperties {
+ method public androidx.compose.ui.semantics.SemanticsPropertyKey<androidx.compose.ui.semantics.CollectionInfo> getCollectionInfo();
+ method public androidx.compose.ui.semantics.SemanticsPropertyKey<androidx.compose.ui.semantics.CollectionItemInfo> getCollectionItemInfo();
method public androidx.compose.ui.semantics.SemanticsPropertyKey<java.lang.String> getContentDescription();
method public androidx.compose.ui.semantics.SemanticsPropertyKey<kotlin.Unit> getDisabled();
method public androidx.compose.ui.semantics.SemanticsPropertyKey<androidx.compose.ui.text.AnnotatedString> getEditableText();
@@ -2305,6 +2334,8 @@
method public androidx.compose.ui.semantics.SemanticsPropertyKey<androidx.compose.ui.text.TextRange> getTextSelectionRange();
method public androidx.compose.ui.semantics.SemanticsPropertyKey<androidx.compose.ui.state.ToggleableState> getToggleableState();
method public androidx.compose.ui.semantics.SemanticsPropertyKey<androidx.compose.ui.semantics.ScrollAxisRange> getVerticalScrollAxisRange();
+ property public final androidx.compose.ui.semantics.SemanticsPropertyKey<androidx.compose.ui.semantics.CollectionInfo> CollectionInfo;
+ property public final androidx.compose.ui.semantics.SemanticsPropertyKey<androidx.compose.ui.semantics.CollectionItemInfo> CollectionItemInfo;
property public final androidx.compose.ui.semantics.SemanticsPropertyKey<java.lang.String> ContentDescription;
property public final androidx.compose.ui.semantics.SemanticsPropertyKey<kotlin.Unit> Disabled;
property public final androidx.compose.ui.semantics.SemanticsPropertyKey<androidx.compose.ui.text.AnnotatedString> EditableText;
@@ -2342,6 +2373,8 @@
method public static void dismiss(androidx.compose.ui.semantics.SemanticsPropertyReceiver, optional String? label, kotlin.jvm.functions.Function0<java.lang.Boolean>? action);
method public static void error(androidx.compose.ui.semantics.SemanticsPropertyReceiver, String description);
method public static void expand(androidx.compose.ui.semantics.SemanticsPropertyReceiver, optional String? label, kotlin.jvm.functions.Function0<java.lang.Boolean>? action);
+ method public static androidx.compose.ui.semantics.CollectionInfo getCollectionInfo(androidx.compose.ui.semantics.SemanticsPropertyReceiver);
+ method public static androidx.compose.ui.semantics.CollectionItemInfo getCollectionItemInfo(androidx.compose.ui.semantics.SemanticsPropertyReceiver);
method public static String getContentDescription(androidx.compose.ui.semantics.SemanticsPropertyReceiver);
method public static java.util.List<androidx.compose.ui.semantics.CustomAccessibilityAction> getCustomActions(androidx.compose.ui.semantics.SemanticsPropertyReceiver);
method public static androidx.compose.ui.text.AnnotatedString getEditableText(androidx.compose.ui.semantics.SemanticsPropertyReceiver);
@@ -2370,6 +2403,8 @@
method public static void scrollBy(androidx.compose.ui.semantics.SemanticsPropertyReceiver, optional String? label, kotlin.jvm.functions.Function2<? super java.lang.Float,? super java.lang.Float,java.lang.Boolean>? action);
method public static void scrollToIndex(androidx.compose.ui.semantics.SemanticsPropertyReceiver, optional String? label, kotlin.jvm.functions.Function1<? super java.lang.Integer,java.lang.Boolean> action);
method public static void selectableGroup(androidx.compose.ui.semantics.SemanticsPropertyReceiver);
+ method public static void setCollectionInfo(androidx.compose.ui.semantics.SemanticsPropertyReceiver, androidx.compose.ui.semantics.CollectionInfo p);
+ method public static void setCollectionItemInfo(androidx.compose.ui.semantics.SemanticsPropertyReceiver, androidx.compose.ui.semantics.CollectionItemInfo p);
method public static void setContentDescription(androidx.compose.ui.semantics.SemanticsPropertyReceiver, String p);
method public static void setCustomActions(androidx.compose.ui.semantics.SemanticsPropertyReceiver, java.util.List<androidx.compose.ui.semantics.CustomAccessibilityAction> p);
method public static void setEditableText(androidx.compose.ui.semantics.SemanticsPropertyReceiver, androidx.compose.ui.text.AnnotatedString p);
diff --git a/compose/ui/ui/api/public_plus_experimental_1.0.0-beta06.txt b/compose/ui/ui/api/public_plus_experimental_1.0.0-beta06.txt
index 81cc288..535178e 100644
--- a/compose/ui/ui/api/public_plus_experimental_1.0.0-beta06.txt
+++ b/compose/ui/ui/api/public_plus_experimental_1.0.0-beta06.txt
@@ -2197,6 +2197,13 @@
}
+package androidx.compose.ui.platform.accessibility {
+
+ public final class CollectionInfoKt {
+ }
+
+}
+
package androidx.compose.ui.platform.actionmodecallback {
public final class TextActionModeCallback_androidKt {
@@ -2260,6 +2267,26 @@
property public final String? label;
}
+ public final class CollectionInfo {
+ ctor public CollectionInfo(int rowCount, int columnCount);
+ method public int getColumnCount();
+ method public int getRowCount();
+ property public final int columnCount;
+ property public final int rowCount;
+ }
+
+ public final class CollectionItemInfo {
+ ctor public CollectionItemInfo(int rowIndex, int rowSpan, int columnIndex, int columnSpan);
+ method public int getColumnIndex();
+ method public int getColumnSpan();
+ method public int getRowIndex();
+ method public int getRowSpan();
+ property public final int columnIndex;
+ property public final int columnSpan;
+ property public final int rowIndex;
+ property public final int rowSpan;
+ }
+
public final class CustomAccessibilityAction {
ctor public CustomAccessibilityAction(String label, kotlin.jvm.functions.Function0<java.lang.Boolean> action);
method public kotlin.jvm.functions.Function0<java.lang.Boolean> getAction();
@@ -2420,6 +2447,8 @@
}
public final class SemanticsProperties {
+ method public androidx.compose.ui.semantics.SemanticsPropertyKey<androidx.compose.ui.semantics.CollectionInfo> getCollectionInfo();
+ method public androidx.compose.ui.semantics.SemanticsPropertyKey<androidx.compose.ui.semantics.CollectionItemInfo> getCollectionItemInfo();
method public androidx.compose.ui.semantics.SemanticsPropertyKey<java.lang.String> getContentDescription();
method public androidx.compose.ui.semantics.SemanticsPropertyKey<kotlin.Unit> getDisabled();
method public androidx.compose.ui.semantics.SemanticsPropertyKey<androidx.compose.ui.text.AnnotatedString> getEditableText();
@@ -2445,6 +2474,8 @@
method public androidx.compose.ui.semantics.SemanticsPropertyKey<androidx.compose.ui.text.TextRange> getTextSelectionRange();
method public androidx.compose.ui.semantics.SemanticsPropertyKey<androidx.compose.ui.state.ToggleableState> getToggleableState();
method public androidx.compose.ui.semantics.SemanticsPropertyKey<androidx.compose.ui.semantics.ScrollAxisRange> getVerticalScrollAxisRange();
+ property public final androidx.compose.ui.semantics.SemanticsPropertyKey<androidx.compose.ui.semantics.CollectionInfo> CollectionInfo;
+ property public final androidx.compose.ui.semantics.SemanticsPropertyKey<androidx.compose.ui.semantics.CollectionItemInfo> CollectionItemInfo;
property public final androidx.compose.ui.semantics.SemanticsPropertyKey<java.lang.String> ContentDescription;
property public final androidx.compose.ui.semantics.SemanticsPropertyKey<kotlin.Unit> Disabled;
property public final androidx.compose.ui.semantics.SemanticsPropertyKey<androidx.compose.ui.text.AnnotatedString> EditableText;
@@ -2482,6 +2513,8 @@
method public static void dismiss(androidx.compose.ui.semantics.SemanticsPropertyReceiver, optional String? label, kotlin.jvm.functions.Function0<java.lang.Boolean>? action);
method public static void error(androidx.compose.ui.semantics.SemanticsPropertyReceiver, String description);
method public static void expand(androidx.compose.ui.semantics.SemanticsPropertyReceiver, optional String? label, kotlin.jvm.functions.Function0<java.lang.Boolean>? action);
+ method public static androidx.compose.ui.semantics.CollectionInfo getCollectionInfo(androidx.compose.ui.semantics.SemanticsPropertyReceiver);
+ method public static androidx.compose.ui.semantics.CollectionItemInfo getCollectionItemInfo(androidx.compose.ui.semantics.SemanticsPropertyReceiver);
method public static String getContentDescription(androidx.compose.ui.semantics.SemanticsPropertyReceiver);
method public static java.util.List<androidx.compose.ui.semantics.CustomAccessibilityAction> getCustomActions(androidx.compose.ui.semantics.SemanticsPropertyReceiver);
method public static androidx.compose.ui.text.AnnotatedString getEditableText(androidx.compose.ui.semantics.SemanticsPropertyReceiver);
@@ -2511,6 +2544,8 @@
method public static void scrollBy(androidx.compose.ui.semantics.SemanticsPropertyReceiver, optional String? label, kotlin.jvm.functions.Function2<? super java.lang.Float,? super java.lang.Float,java.lang.Boolean>? action);
method public static void scrollToIndex(androidx.compose.ui.semantics.SemanticsPropertyReceiver, optional String? label, kotlin.jvm.functions.Function1<? super java.lang.Integer,java.lang.Boolean> action);
method public static void selectableGroup(androidx.compose.ui.semantics.SemanticsPropertyReceiver);
+ method public static void setCollectionInfo(androidx.compose.ui.semantics.SemanticsPropertyReceiver, androidx.compose.ui.semantics.CollectionInfo p);
+ method public static void setCollectionItemInfo(androidx.compose.ui.semantics.SemanticsPropertyReceiver, androidx.compose.ui.semantics.CollectionItemInfo p);
method public static void setContentDescription(androidx.compose.ui.semantics.SemanticsPropertyReceiver, String p);
method public static void setCustomActions(androidx.compose.ui.semantics.SemanticsPropertyReceiver, java.util.List<androidx.compose.ui.semantics.CustomAccessibilityAction> p);
method public static void setEditableText(androidx.compose.ui.semantics.SemanticsPropertyReceiver, androidx.compose.ui.text.AnnotatedString p);
diff --git a/compose/ui/ui/api/public_plus_experimental_current.txt b/compose/ui/ui/api/public_plus_experimental_current.txt
index 81cc288..535178e 100644
--- a/compose/ui/ui/api/public_plus_experimental_current.txt
+++ b/compose/ui/ui/api/public_plus_experimental_current.txt
@@ -2197,6 +2197,13 @@
}
+package androidx.compose.ui.platform.accessibility {
+
+ public final class CollectionInfoKt {
+ }
+
+}
+
package androidx.compose.ui.platform.actionmodecallback {
public final class TextActionModeCallback_androidKt {
@@ -2260,6 +2267,26 @@
property public final String? label;
}
+ public final class CollectionInfo {
+ ctor public CollectionInfo(int rowCount, int columnCount);
+ method public int getColumnCount();
+ method public int getRowCount();
+ property public final int columnCount;
+ property public final int rowCount;
+ }
+
+ public final class CollectionItemInfo {
+ ctor public CollectionItemInfo(int rowIndex, int rowSpan, int columnIndex, int columnSpan);
+ method public int getColumnIndex();
+ method public int getColumnSpan();
+ method public int getRowIndex();
+ method public int getRowSpan();
+ property public final int columnIndex;
+ property public final int columnSpan;
+ property public final int rowIndex;
+ property public final int rowSpan;
+ }
+
public final class CustomAccessibilityAction {
ctor public CustomAccessibilityAction(String label, kotlin.jvm.functions.Function0<java.lang.Boolean> action);
method public kotlin.jvm.functions.Function0<java.lang.Boolean> getAction();
@@ -2420,6 +2447,8 @@
}
public final class SemanticsProperties {
+ method public androidx.compose.ui.semantics.SemanticsPropertyKey<androidx.compose.ui.semantics.CollectionInfo> getCollectionInfo();
+ method public androidx.compose.ui.semantics.SemanticsPropertyKey<androidx.compose.ui.semantics.CollectionItemInfo> getCollectionItemInfo();
method public androidx.compose.ui.semantics.SemanticsPropertyKey<java.lang.String> getContentDescription();
method public androidx.compose.ui.semantics.SemanticsPropertyKey<kotlin.Unit> getDisabled();
method public androidx.compose.ui.semantics.SemanticsPropertyKey<androidx.compose.ui.text.AnnotatedString> getEditableText();
@@ -2445,6 +2474,8 @@
method public androidx.compose.ui.semantics.SemanticsPropertyKey<androidx.compose.ui.text.TextRange> getTextSelectionRange();
method public androidx.compose.ui.semantics.SemanticsPropertyKey<androidx.compose.ui.state.ToggleableState> getToggleableState();
method public androidx.compose.ui.semantics.SemanticsPropertyKey<androidx.compose.ui.semantics.ScrollAxisRange> getVerticalScrollAxisRange();
+ property public final androidx.compose.ui.semantics.SemanticsPropertyKey<androidx.compose.ui.semantics.CollectionInfo> CollectionInfo;
+ property public final androidx.compose.ui.semantics.SemanticsPropertyKey<androidx.compose.ui.semantics.CollectionItemInfo> CollectionItemInfo;
property public final androidx.compose.ui.semantics.SemanticsPropertyKey<java.lang.String> ContentDescription;
property public final androidx.compose.ui.semantics.SemanticsPropertyKey<kotlin.Unit> Disabled;
property public final androidx.compose.ui.semantics.SemanticsPropertyKey<androidx.compose.ui.text.AnnotatedString> EditableText;
@@ -2482,6 +2513,8 @@
method public static void dismiss(androidx.compose.ui.semantics.SemanticsPropertyReceiver, optional String? label, kotlin.jvm.functions.Function0<java.lang.Boolean>? action);
method public static void error(androidx.compose.ui.semantics.SemanticsPropertyReceiver, String description);
method public static void expand(androidx.compose.ui.semantics.SemanticsPropertyReceiver, optional String? label, kotlin.jvm.functions.Function0<java.lang.Boolean>? action);
+ method public static androidx.compose.ui.semantics.CollectionInfo getCollectionInfo(androidx.compose.ui.semantics.SemanticsPropertyReceiver);
+ method public static androidx.compose.ui.semantics.CollectionItemInfo getCollectionItemInfo(androidx.compose.ui.semantics.SemanticsPropertyReceiver);
method public static String getContentDescription(androidx.compose.ui.semantics.SemanticsPropertyReceiver);
method public static java.util.List<androidx.compose.ui.semantics.CustomAccessibilityAction> getCustomActions(androidx.compose.ui.semantics.SemanticsPropertyReceiver);
method public static androidx.compose.ui.text.AnnotatedString getEditableText(androidx.compose.ui.semantics.SemanticsPropertyReceiver);
@@ -2511,6 +2544,8 @@
method public static void scrollBy(androidx.compose.ui.semantics.SemanticsPropertyReceiver, optional String? label, kotlin.jvm.functions.Function2<? super java.lang.Float,? super java.lang.Float,java.lang.Boolean>? action);
method public static void scrollToIndex(androidx.compose.ui.semantics.SemanticsPropertyReceiver, optional String? label, kotlin.jvm.functions.Function1<? super java.lang.Integer,java.lang.Boolean> action);
method public static void selectableGroup(androidx.compose.ui.semantics.SemanticsPropertyReceiver);
+ method public static void setCollectionInfo(androidx.compose.ui.semantics.SemanticsPropertyReceiver, androidx.compose.ui.semantics.CollectionInfo p);
+ method public static void setCollectionItemInfo(androidx.compose.ui.semantics.SemanticsPropertyReceiver, androidx.compose.ui.semantics.CollectionItemInfo p);
method public static void setContentDescription(androidx.compose.ui.semantics.SemanticsPropertyReceiver, String p);
method public static void setCustomActions(androidx.compose.ui.semantics.SemanticsPropertyReceiver, java.util.List<androidx.compose.ui.semantics.CustomAccessibilityAction> p);
method public static void setEditableText(androidx.compose.ui.semantics.SemanticsPropertyReceiver, androidx.compose.ui.text.AnnotatedString p);
diff --git a/compose/ui/ui/api/restricted_1.0.0-beta06.txt b/compose/ui/ui/api/restricted_1.0.0-beta06.txt
index 4760263..84c7fa3 100644
--- a/compose/ui/ui/api/restricted_1.0.0-beta06.txt
+++ b/compose/ui/ui/api/restricted_1.0.0-beta06.txt
@@ -2088,6 +2088,13 @@
}
+package androidx.compose.ui.platform.accessibility {
+
+ public final class CollectionInfoKt {
+ }
+
+}
+
package androidx.compose.ui.platform.actionmodecallback {
public final class TextActionModeCallback_androidKt {
@@ -2150,6 +2157,26 @@
property public final String? label;
}
+ public final class CollectionInfo {
+ ctor public CollectionInfo(int rowCount, int columnCount);
+ method public int getColumnCount();
+ method public int getRowCount();
+ property public final int columnCount;
+ property public final int rowCount;
+ }
+
+ public final class CollectionItemInfo {
+ ctor public CollectionItemInfo(int rowIndex, int rowSpan, int columnIndex, int columnSpan);
+ method public int getColumnIndex();
+ method public int getColumnSpan();
+ method public int getRowIndex();
+ method public int getRowSpan();
+ property public final int columnIndex;
+ property public final int columnSpan;
+ property public final int rowIndex;
+ property public final int rowSpan;
+ }
+
public final class CustomAccessibilityAction {
ctor public CustomAccessibilityAction(String label, kotlin.jvm.functions.Function0<java.lang.Boolean> action);
method public kotlin.jvm.functions.Function0<java.lang.Boolean> getAction();
@@ -2310,6 +2337,8 @@
}
public final class SemanticsProperties {
+ method public androidx.compose.ui.semantics.SemanticsPropertyKey<androidx.compose.ui.semantics.CollectionInfo> getCollectionInfo();
+ method public androidx.compose.ui.semantics.SemanticsPropertyKey<androidx.compose.ui.semantics.CollectionItemInfo> getCollectionItemInfo();
method public androidx.compose.ui.semantics.SemanticsPropertyKey<java.lang.String> getContentDescription();
method public androidx.compose.ui.semantics.SemanticsPropertyKey<kotlin.Unit> getDisabled();
method public androidx.compose.ui.semantics.SemanticsPropertyKey<androidx.compose.ui.text.AnnotatedString> getEditableText();
@@ -2335,6 +2364,8 @@
method public androidx.compose.ui.semantics.SemanticsPropertyKey<androidx.compose.ui.text.TextRange> getTextSelectionRange();
method public androidx.compose.ui.semantics.SemanticsPropertyKey<androidx.compose.ui.state.ToggleableState> getToggleableState();
method public androidx.compose.ui.semantics.SemanticsPropertyKey<androidx.compose.ui.semantics.ScrollAxisRange> getVerticalScrollAxisRange();
+ property public final androidx.compose.ui.semantics.SemanticsPropertyKey<androidx.compose.ui.semantics.CollectionInfo> CollectionInfo;
+ property public final androidx.compose.ui.semantics.SemanticsPropertyKey<androidx.compose.ui.semantics.CollectionItemInfo> CollectionItemInfo;
property public final androidx.compose.ui.semantics.SemanticsPropertyKey<java.lang.String> ContentDescription;
property public final androidx.compose.ui.semantics.SemanticsPropertyKey<kotlin.Unit> Disabled;
property public final androidx.compose.ui.semantics.SemanticsPropertyKey<androidx.compose.ui.text.AnnotatedString> EditableText;
@@ -2372,6 +2403,8 @@
method public static void dismiss(androidx.compose.ui.semantics.SemanticsPropertyReceiver, optional String? label, kotlin.jvm.functions.Function0<java.lang.Boolean>? action);
method public static void error(androidx.compose.ui.semantics.SemanticsPropertyReceiver, String description);
method public static void expand(androidx.compose.ui.semantics.SemanticsPropertyReceiver, optional String? label, kotlin.jvm.functions.Function0<java.lang.Boolean>? action);
+ method public static androidx.compose.ui.semantics.CollectionInfo getCollectionInfo(androidx.compose.ui.semantics.SemanticsPropertyReceiver);
+ method public static androidx.compose.ui.semantics.CollectionItemInfo getCollectionItemInfo(androidx.compose.ui.semantics.SemanticsPropertyReceiver);
method public static String getContentDescription(androidx.compose.ui.semantics.SemanticsPropertyReceiver);
method public static java.util.List<androidx.compose.ui.semantics.CustomAccessibilityAction> getCustomActions(androidx.compose.ui.semantics.SemanticsPropertyReceiver);
method public static androidx.compose.ui.text.AnnotatedString getEditableText(androidx.compose.ui.semantics.SemanticsPropertyReceiver);
@@ -2400,6 +2433,8 @@
method public static void scrollBy(androidx.compose.ui.semantics.SemanticsPropertyReceiver, optional String? label, kotlin.jvm.functions.Function2<? super java.lang.Float,? super java.lang.Float,java.lang.Boolean>? action);
method public static void scrollToIndex(androidx.compose.ui.semantics.SemanticsPropertyReceiver, optional String? label, kotlin.jvm.functions.Function1<? super java.lang.Integer,java.lang.Boolean> action);
method public static void selectableGroup(androidx.compose.ui.semantics.SemanticsPropertyReceiver);
+ method public static void setCollectionInfo(androidx.compose.ui.semantics.SemanticsPropertyReceiver, androidx.compose.ui.semantics.CollectionInfo p);
+ method public static void setCollectionItemInfo(androidx.compose.ui.semantics.SemanticsPropertyReceiver, androidx.compose.ui.semantics.CollectionItemInfo p);
method public static void setContentDescription(androidx.compose.ui.semantics.SemanticsPropertyReceiver, String p);
method public static void setCustomActions(androidx.compose.ui.semantics.SemanticsPropertyReceiver, java.util.List<androidx.compose.ui.semantics.CustomAccessibilityAction> p);
method public static void setEditableText(androidx.compose.ui.semantics.SemanticsPropertyReceiver, androidx.compose.ui.text.AnnotatedString p);
diff --git a/compose/ui/ui/api/restricted_current.txt b/compose/ui/ui/api/restricted_current.txt
index 4760263..84c7fa3 100644
--- a/compose/ui/ui/api/restricted_current.txt
+++ b/compose/ui/ui/api/restricted_current.txt
@@ -2088,6 +2088,13 @@
}
+package androidx.compose.ui.platform.accessibility {
+
+ public final class CollectionInfoKt {
+ }
+
+}
+
package androidx.compose.ui.platform.actionmodecallback {
public final class TextActionModeCallback_androidKt {
@@ -2150,6 +2157,26 @@
property public final String? label;
}
+ public final class CollectionInfo {
+ ctor public CollectionInfo(int rowCount, int columnCount);
+ method public int getColumnCount();
+ method public int getRowCount();
+ property public final int columnCount;
+ property public final int rowCount;
+ }
+
+ public final class CollectionItemInfo {
+ ctor public CollectionItemInfo(int rowIndex, int rowSpan, int columnIndex, int columnSpan);
+ method public int getColumnIndex();
+ method public int getColumnSpan();
+ method public int getRowIndex();
+ method public int getRowSpan();
+ property public final int columnIndex;
+ property public final int columnSpan;
+ property public final int rowIndex;
+ property public final int rowSpan;
+ }
+
public final class CustomAccessibilityAction {
ctor public CustomAccessibilityAction(String label, kotlin.jvm.functions.Function0<java.lang.Boolean> action);
method public kotlin.jvm.functions.Function0<java.lang.Boolean> getAction();
@@ -2310,6 +2337,8 @@
}
public final class SemanticsProperties {
+ method public androidx.compose.ui.semantics.SemanticsPropertyKey<androidx.compose.ui.semantics.CollectionInfo> getCollectionInfo();
+ method public androidx.compose.ui.semantics.SemanticsPropertyKey<androidx.compose.ui.semantics.CollectionItemInfo> getCollectionItemInfo();
method public androidx.compose.ui.semantics.SemanticsPropertyKey<java.lang.String> getContentDescription();
method public androidx.compose.ui.semantics.SemanticsPropertyKey<kotlin.Unit> getDisabled();
method public androidx.compose.ui.semantics.SemanticsPropertyKey<androidx.compose.ui.text.AnnotatedString> getEditableText();
@@ -2335,6 +2364,8 @@
method public androidx.compose.ui.semantics.SemanticsPropertyKey<androidx.compose.ui.text.TextRange> getTextSelectionRange();
method public androidx.compose.ui.semantics.SemanticsPropertyKey<androidx.compose.ui.state.ToggleableState> getToggleableState();
method public androidx.compose.ui.semantics.SemanticsPropertyKey<androidx.compose.ui.semantics.ScrollAxisRange> getVerticalScrollAxisRange();
+ property public final androidx.compose.ui.semantics.SemanticsPropertyKey<androidx.compose.ui.semantics.CollectionInfo> CollectionInfo;
+ property public final androidx.compose.ui.semantics.SemanticsPropertyKey<androidx.compose.ui.semantics.CollectionItemInfo> CollectionItemInfo;
property public final androidx.compose.ui.semantics.SemanticsPropertyKey<java.lang.String> ContentDescription;
property public final androidx.compose.ui.semantics.SemanticsPropertyKey<kotlin.Unit> Disabled;
property public final androidx.compose.ui.semantics.SemanticsPropertyKey<androidx.compose.ui.text.AnnotatedString> EditableText;
@@ -2372,6 +2403,8 @@
method public static void dismiss(androidx.compose.ui.semantics.SemanticsPropertyReceiver, optional String? label, kotlin.jvm.functions.Function0<java.lang.Boolean>? action);
method public static void error(androidx.compose.ui.semantics.SemanticsPropertyReceiver, String description);
method public static void expand(androidx.compose.ui.semantics.SemanticsPropertyReceiver, optional String? label, kotlin.jvm.functions.Function0<java.lang.Boolean>? action);
+ method public static androidx.compose.ui.semantics.CollectionInfo getCollectionInfo(androidx.compose.ui.semantics.SemanticsPropertyReceiver);
+ method public static androidx.compose.ui.semantics.CollectionItemInfo getCollectionItemInfo(androidx.compose.ui.semantics.SemanticsPropertyReceiver);
method public static String getContentDescription(androidx.compose.ui.semantics.SemanticsPropertyReceiver);
method public static java.util.List<androidx.compose.ui.semantics.CustomAccessibilityAction> getCustomActions(androidx.compose.ui.semantics.SemanticsPropertyReceiver);
method public static androidx.compose.ui.text.AnnotatedString getEditableText(androidx.compose.ui.semantics.SemanticsPropertyReceiver);
@@ -2400,6 +2433,8 @@
method public static void scrollBy(androidx.compose.ui.semantics.SemanticsPropertyReceiver, optional String? label, kotlin.jvm.functions.Function2<? super java.lang.Float,? super java.lang.Float,java.lang.Boolean>? action);
method public static void scrollToIndex(androidx.compose.ui.semantics.SemanticsPropertyReceiver, optional String? label, kotlin.jvm.functions.Function1<? super java.lang.Integer,java.lang.Boolean> action);
method public static void selectableGroup(androidx.compose.ui.semantics.SemanticsPropertyReceiver);
+ method public static void setCollectionInfo(androidx.compose.ui.semantics.SemanticsPropertyReceiver, androidx.compose.ui.semantics.CollectionInfo p);
+ method public static void setCollectionItemInfo(androidx.compose.ui.semantics.SemanticsPropertyReceiver, androidx.compose.ui.semantics.CollectionItemInfo p);
method public static void setContentDescription(androidx.compose.ui.semantics.SemanticsPropertyReceiver, String p);
method public static void setCustomActions(androidx.compose.ui.semantics.SemanticsPropertyReceiver, java.util.List<androidx.compose.ui.semantics.CustomAccessibilityAction> p);
method public static void setEditableText(androidx.compose.ui.semantics.SemanticsPropertyReceiver, androidx.compose.ui.text.AnnotatedString p);
diff --git a/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/AndroidComposeViewAccessibilityDelegateCompatTest.kt b/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/AndroidComposeViewAccessibilityDelegateCompatTest.kt
index 6a41110..653c6ca 100644
--- a/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/AndroidComposeViewAccessibilityDelegateCompatTest.kt
+++ b/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/AndroidComposeViewAccessibilityDelegateCompatTest.kt
@@ -24,10 +24,7 @@
import android.view.accessibility.AccessibilityNodeInfo
import android.widget.FrameLayout
import androidx.compose.foundation.layout.Box
-import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.size
-import androidx.compose.foundation.selection.selectable
-import androidx.compose.foundation.selection.selectableGroup
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.setValue
@@ -848,46 +845,6 @@
}
@Test
- fun testCollectionItemInfo() {
- rule.setContent {
- Column(Modifier.selectableGroup()) {
- Box(Modifier.selectable(selected = true, onClick = {}).testTag("item"))
- Box(Modifier.selectable(selected = false, onClick = {}))
- }
- }
- val itemNode = rule.onNodeWithTag("item").fetchSemanticsNode()
- accessibilityDelegate.populateAccessibilityNodeInfoProperties(1, info, itemNode)
-
- val resultCollectionItemInfo = info.collectionItemInfo
- assertEquals(0, resultCollectionItemInfo.rowIndex)
- assertEquals(1, resultCollectionItemInfo.rowSpan)
- assertEquals(0, resultCollectionItemInfo.columnIndex)
- assertEquals(1, resultCollectionItemInfo.columnSpan)
- assertEquals(true, resultCollectionItemInfo.isSelected)
- }
-
- @Test
- fun testCollectionInfo() {
- rule.setContent {
- Column(Modifier.selectableGroup().testTag("collection")) {
- Box(Modifier.size(50.dp).selectable(selected = true, onClick = {}))
- Box(Modifier.size(50.dp).selectable(selected = false, onClick = {}))
- }
- }
- val collectionNode = rule.onNodeWithTag("collection").fetchSemanticsNode()
- accessibilityDelegate.populateAccessibilityNodeInfoProperties(1, info, collectionNode)
-
- val resultCollectionInfo = info.collectionInfo
- assertEquals(2, resultCollectionInfo.rowCount)
- assertEquals(1, resultCollectionInfo.columnCount)
- assertEquals(false, resultCollectionInfo.isHierarchical)
- assertEquals(
- AccessibilityNodeInfoCompat.CollectionInfoCompat.SELECTION_MODE_SINGLE,
- resultCollectionInfo.selectionMode
- )
- }
-
- @Test
fun testSemanticsNodePositionAndBounds_doesNotThrow_whenLayoutNodeNotAttached() {
var emitNode by mutableStateOf(true)
rule.setContent {
diff --git a/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/accessibility/CollectionInfoTest.kt b/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/accessibility/CollectionInfoTest.kt
new file mode 100644
index 0000000..d14771c
--- /dev/null
+++ b/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/accessibility/CollectionInfoTest.kt
@@ -0,0 +1,297 @@
+/*
+ * Copyright 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.compose.ui.accessibility
+
+import android.view.ViewGroup
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.size
+import androidx.compose.foundation.lazy.LazyColumn
+import androidx.compose.foundation.lazy.itemsIndexed
+import androidx.compose.foundation.selection.selectable
+import androidx.compose.foundation.selection.selectableGroup
+import androidx.compose.foundation.text.BasicText
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.OpenComposeView
+import androidx.compose.ui.platform.AndroidComposeView
+import androidx.compose.ui.platform.AndroidComposeViewAccessibilityDelegateCompat
+import androidx.compose.ui.platform.testTag
+import androidx.compose.ui.semantics.CollectionInfo
+import androidx.compose.ui.semantics.CollectionItemInfo
+import androidx.compose.ui.semantics.collectionInfo
+import androidx.compose.ui.semantics.collectionItemInfo
+import androidx.compose.ui.semantics.semantics
+import androidx.compose.ui.test.TestActivity
+import androidx.compose.ui.test.junit4.createAndroidComposeRule
+import androidx.compose.ui.test.onNodeWithTag
+import androidx.compose.ui.test.onNodeWithText
+import androidx.compose.ui.unit.dp
+import androidx.core.view.accessibility.AccessibilityNodeInfoCompat
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.LargeTest
+import org.junit.After
+import org.junit.Assert
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@LargeTest
+@RunWith(AndroidJUnit4::class)
+class CollectionInfoTest {
+ @get:Rule
+ val rule = createAndroidComposeRule<TestActivity>()
+
+ private lateinit var accessibilityDelegate: AndroidComposeViewAccessibilityDelegateCompat
+ private lateinit var info: AccessibilityNodeInfoCompat
+
+ @Before
+ fun setup() {
+ val container = OpenComposeView(rule.activity)
+
+ rule.runOnUiThread {
+ rule.activity.setContentView(
+ container,
+ ViewGroup.LayoutParams(
+ ViewGroup.LayoutParams.WRAP_CONTENT,
+ ViewGroup.LayoutParams.WRAP_CONTENT
+ )
+ )
+ }
+
+ val composeView = container.getChildAt(0) as AndroidComposeView
+ accessibilityDelegate = AndroidComposeViewAccessibilityDelegateCompat(composeView).apply {
+ accessibilityForceEnabledForTesting = true
+ }
+ info = AccessibilityNodeInfoCompat.obtain()
+ }
+
+ @After
+ fun cleanup() {
+ info.recycle()
+ }
+
+ // Collection Info tests
+ @Test
+ fun testCollectionInfo_withSelectableGroup() {
+ rule.setContent {
+ Column(Modifier.selectableGroup().testTag("collection")) {
+ Box(Modifier.size(50.dp).selectable(selected = true, onClick = {}))
+ Box(Modifier.size(50.dp).selectable(selected = false, onClick = {}))
+ }
+ }
+ val collectionNode = rule.onNodeWithTag("collection").fetchSemanticsNode()
+ accessibilityDelegate
+ .populateAccessibilityNodeInfoProperties(collectionNode.id, info, collectionNode)
+
+ val resultCollectionInfo = info.collectionInfo
+ Assert.assertEquals(2, resultCollectionInfo.rowCount)
+ Assert.assertEquals(1, resultCollectionInfo.columnCount)
+ Assert.assertEquals(false, resultCollectionInfo.isHierarchical)
+ }
+
+ @Test
+ fun testDefaultCollectionInfo_lazyList() {
+ val tag = "LazyColumn"
+ rule.setContent {
+ LazyColumn(Modifier.testTag(tag)) {
+ items(2) { BasicText("Text") }
+ }
+ }
+
+ val itemNode = rule.onNodeWithTag(tag).fetchSemanticsNode()
+ accessibilityDelegate.populateAccessibilityNodeInfoProperties(itemNode.id, info, itemNode)
+
+ val resultCollectionInfo = info.collectionInfo
+ Assert.assertEquals(-1, resultCollectionInfo.rowCount)
+ Assert.assertEquals(1, resultCollectionInfo.columnCount)
+ Assert.assertEquals(false, resultCollectionInfo.isHierarchical)
+ }
+
+ @Test
+ fun testCollectionInfo_lazyList() {
+ val tag = "LazyColumn"
+ rule.setContent {
+ LazyColumn(
+ Modifier
+ .testTag(tag)
+ .semantics { collectionInfo = CollectionInfo(2, 1) }
+ ) {
+ items(2) { BasicText("Text") }
+ }
+ }
+
+ val itemNode = rule.onNodeWithTag(tag).fetchSemanticsNode()
+ accessibilityDelegate.populateAccessibilityNodeInfoProperties(itemNode.id, info, itemNode)
+
+ val resultCollectionInfo = info.collectionInfo
+ Assert.assertEquals(2, resultCollectionInfo.rowCount)
+ Assert.assertEquals(1, resultCollectionInfo.columnCount)
+ Assert.assertEquals(false, resultCollectionInfo.isHierarchical)
+ }
+
+ @Test
+ fun testCollectionInfo_withSelectableGroup_andDefaultLazyListSemantics() {
+ val tag = "LazyColumn"
+ rule.setContent {
+ LazyColumn(Modifier.testTag(tag).selectableGroup()) {
+ items(2) { BasicText("Text") }
+ }
+ }
+
+ val itemNode = rule.onNodeWithTag(tag).fetchSemanticsNode()
+ accessibilityDelegate.populateAccessibilityNodeInfoProperties(itemNode.id, info, itemNode)
+
+ val resultCollectionInfo = info.collectionInfo
+ Assert.assertEquals(-1, resultCollectionInfo.rowCount)
+ Assert.assertEquals(1, resultCollectionInfo.columnCount)
+ Assert.assertEquals(false, resultCollectionInfo.isHierarchical)
+ }
+
+ @Test
+ fun testCollectionInfo_withSelectableGroup_andLazyListSemantics() {
+ val tag = "LazyColumn"
+ rule.setContent {
+ LazyColumn(
+ Modifier
+ .testTag(tag)
+ .selectableGroup()
+ .semantics { collectionInfo = CollectionInfo(2, 1) }
+ ) {
+ items(2) { BasicText("Text") }
+ }
+ }
+
+ val itemNode = rule.onNodeWithTag(tag).fetchSemanticsNode()
+ accessibilityDelegate.populateAccessibilityNodeInfoProperties(itemNode.id, info, itemNode)
+
+ val resultCollectionInfo = info.collectionInfo
+ Assert.assertEquals(2, resultCollectionInfo.rowCount)
+ Assert.assertEquals(1, resultCollectionInfo.columnCount)
+ Assert.assertEquals(false, resultCollectionInfo.isHierarchical)
+ }
+
+ // Collection Item Info tests
+ @Test
+ fun testCollectionItemInfo_withSelectableGroup() {
+ rule.setContent {
+ Column(Modifier.selectableGroup()) {
+ Box(Modifier.selectable(selected = true, onClick = {}).testTag("item"))
+ Box(Modifier.selectable(selected = false, onClick = {}))
+ }
+ }
+
+ val itemNode = rule.onNodeWithTag("item").fetchSemanticsNode()
+ accessibilityDelegate.populateAccessibilityNodeInfoProperties(itemNode.id, info, itemNode)
+
+ val resultCollectionItemInfo = info.collectionItemInfo
+ Assert.assertEquals(0, resultCollectionItemInfo.rowIndex)
+ Assert.assertEquals(1, resultCollectionItemInfo.rowSpan)
+ Assert.assertEquals(0, resultCollectionItemInfo.columnIndex)
+ Assert.assertEquals(1, resultCollectionItemInfo.columnSpan)
+ Assert.assertEquals(true, resultCollectionItemInfo.isSelected)
+ }
+
+ @Test
+ fun testNoCollectionItemInfo_lazyList() {
+ rule.setContent {
+ LazyColumn {
+ itemsIndexed(listOf("Text", "Text")) { index, item -> BasicText(item + index) }
+ }
+ }
+
+ val itemNode = rule.onNodeWithText("Text0").fetchSemanticsNode()
+ accessibilityDelegate.populateAccessibilityNodeInfoProperties(itemNode.id, info, itemNode)
+
+ Assert.assertNull(info.collectionItemInfo)
+ }
+
+ @Test
+ fun testCollectionItemInfo_defaultLazyListSemantics() {
+ rule.setContent {
+ LazyColumn {
+ itemsIndexed(listOf("Text", "Text")) { index, item ->
+ BasicText(
+ item + index,
+ Modifier.semantics {
+ collectionItemInfo = CollectionItemInfo(index, 1, 0, 1)
+ }
+ )
+ }
+ }
+ }
+
+ val itemNode = rule.onNodeWithText("Text0").fetchSemanticsNode()
+ accessibilityDelegate.populateAccessibilityNodeInfoProperties(itemNode.id, info, itemNode)
+
+ val resultCollectionItemInfo = info.collectionItemInfo
+ Assert.assertEquals(0, resultCollectionItemInfo.rowIndex)
+ Assert.assertEquals(1, resultCollectionItemInfo.rowSpan)
+ Assert.assertEquals(0, resultCollectionItemInfo.columnIndex)
+ Assert.assertEquals(1, resultCollectionItemInfo.columnSpan)
+ }
+
+ @Test
+ fun testCollectionItemInfo_lazyList() {
+ rule.setContent {
+ LazyColumn(Modifier.semantics { collectionInfo = CollectionInfo(2, 1) }) {
+ itemsIndexed(listOf("Text", "Text")) { index, item ->
+ BasicText(
+ item + index,
+ Modifier.semantics {
+ collectionItemInfo = CollectionItemInfo(index, 1, 0, 1)
+ }
+ )
+ }
+ }
+ }
+
+ val itemNode = rule.onNodeWithText("Text0").fetchSemanticsNode()
+ accessibilityDelegate.populateAccessibilityNodeInfoProperties(itemNode.id, info, itemNode)
+
+ val resultCollectionItemInfo = info.collectionItemInfo
+ Assert.assertEquals(0, resultCollectionItemInfo.rowIndex)
+ Assert.assertEquals(1, resultCollectionItemInfo.rowSpan)
+ Assert.assertEquals(0, resultCollectionItemInfo.columnIndex)
+ Assert.assertEquals(1, resultCollectionItemInfo.columnSpan)
+ }
+
+ @Test
+ fun testCollectionItemInfo_withSelectableGroup_andDefaultLazyListSemantics() {
+ rule.setContent {
+ LazyColumn(Modifier.selectableGroup()) {
+ itemsIndexed(listOf("Text", "Text")) { index, item ->
+ BasicText(
+ item + index,
+ Modifier.semantics {
+ collectionItemInfo = CollectionItemInfo(index, 1, 0, 1)
+ }
+ )
+ }
+ }
+ }
+
+ val itemNode = rule.onNodeWithText("Text0").fetchSemanticsNode()
+ accessibilityDelegate.populateAccessibilityNodeInfoProperties(itemNode.id, info, itemNode)
+
+ val resultCollectionItemInfo = info.collectionItemInfo
+ Assert.assertEquals(0, resultCollectionItemInfo.rowIndex)
+ Assert.assertEquals(1, resultCollectionItemInfo.rowSpan)
+ Assert.assertEquals(0, resultCollectionItemInfo.columnIndex)
+ Assert.assertEquals(1, resultCollectionItemInfo.columnSpan)
+ }
+}
\ No newline at end of file
diff --git a/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/focus/KeyEventToFocusDirectionTest.kt b/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/focus/KeyEventToFocusDirectionTest.kt
index 0e10a62..d389b61 100644
--- a/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/focus/KeyEventToFocusDirectionTest.kt
+++ b/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/focus/KeyEventToFocusDirectionTest.kt
@@ -34,7 +34,6 @@
import androidx.test.filters.SmallTest
import com.google.common.truth.Truth.assertThat
import org.junit.Before
-import org.junit.Ignore
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
@@ -141,7 +140,6 @@
assertThat(focusDirection).isEqualTo(In)
}
- @Ignore("Disabled due to b/185211677")
@Test
fun back_out() {
// Arrange.
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 a75c428..ab7fbac 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
@@ -68,6 +68,7 @@
import androidx.compose.ui.graphics.CanvasHolder
import androidx.compose.ui.hapticfeedback.AndroidHapticFeedback
import androidx.compose.ui.hapticfeedback.HapticFeedback
+import androidx.compose.ui.input.key.Key.Companion.Back
import androidx.compose.ui.input.key.Key.Companion.DirectionCenter
import androidx.compose.ui.input.key.Key.Companion.DirectionDown
import androidx.compose.ui.input.key.Key.Companion.DirectionLeft
@@ -640,11 +641,7 @@
DirectionUp -> Up
DirectionDown -> Down
DirectionCenter -> In
- // TODO(b/183746743): Enable Back after fixing issue with DemoTests (b/185211677).
- // If we use the back button to clear focus, then the demo tests need two Back
- // events when an item is focused. Either remove initial focus from the affected
- // Demos or call clearFocus() before sending the Back key event.
- // Back -> Out
+ Back -> Out
else -> null
}
}
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 984ea09..5f2d9a5 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
@@ -59,9 +59,9 @@
import androidx.compose.ui.util.fastForEach
import androidx.compose.ui.ExperimentalComposeUiApi
import androidx.compose.ui.fastJoinToString
-import androidx.compose.ui.fastReduce
-import androidx.compose.ui.fastZipWithNext
import androidx.compose.ui.geometry.Offset
+import androidx.compose.ui.platform.accessibility.setCollectionInfo
+import androidx.compose.ui.platform.accessibility.setCollectionItemInfo
import androidx.compose.ui.semantics.AccessibilityAction
import androidx.compose.ui.semantics.LiveRegionMode
import androidx.compose.ui.semantics.ProgressBarRangeInfo
@@ -76,7 +76,6 @@
import androidx.core.view.accessibility.AccessibilityNodeProviderCompat
import kotlinx.coroutines.channels.Channel
import kotlinx.coroutines.delay
-import kotlin.math.abs
import kotlin.math.ceil
import kotlin.math.floor
@@ -2045,104 +2044,6 @@
return null
}
- private fun setCollectionInfo(node: SemanticsNode, info: AccessibilityNodeInfoCompat) {
- val groupedChildren = mutableListOf<SemanticsNode>()
-
- if (node.config.getOrNull(SemanticsProperties.SelectableGroup) != null) {
- node.children.fastForEach { childNode ->
- // we assume that Tabs and RadioButtons are not mixed under a single group
- if (childNode.config.contains(SemanticsProperties.Selected)) {
- groupedChildren.add(childNode)
- }
- }
- }
-
- if (groupedChildren.isNotEmpty()) {
- /* When we provide a more complex CollectionInfo object, we will use it to determine
- the number of rows, columns, and selection mode. Currently we assume mutual
- exclusivity and liner layout (aka Column or Row). We determine if the layout is
- horizontal or vertical by checking the bounds of the children
- */
- val isHorizontal = calculateIfHorizontallyStacked(groupedChildren)
- info.setCollectionInfo(
- AccessibilityNodeInfoCompat.CollectionInfoCompat.obtain(
- if (isHorizontal) 1 else groupedChildren.count(),
- if (isHorizontal) groupedChildren.count() else 1,
- false,
- getSelectionMode(groupedChildren)
- )
- )
- }
- }
-
- private fun setCollectionItemInfo(node: SemanticsNode, info: AccessibilityNodeInfoCompat) {
- if (!node.config.contains(SemanticsProperties.Selected)) return
-
- val groupedChildren = mutableListOf<SemanticsNode>()
-
- // for "tab" item find all siblings to calculate the index
- val parentNode = node.parent ?: return
- if (parentNode.config.getOrNull(SemanticsProperties.SelectableGroup) != null) {
- // find all siblings to calculate the index
- parentNode.children.fastForEach { childNode ->
- if (childNode.config.contains(SemanticsProperties.Selected)) {
- groupedChildren.add(childNode)
- }
- }
- }
-
- if (groupedChildren.isNotEmpty()) {
- val isHorizontal = calculateIfHorizontallyStacked(groupedChildren)
-
- groupedChildren.fastForEachIndexed { index, tabNode ->
- if (tabNode.id == node.id) {
- val itemInfo = AccessibilityNodeInfoCompat.CollectionItemInfoCompat.obtain(
- if (isHorizontal) 0 else index,
- 1,
- if (isHorizontal) index else 0,
- 1,
- false,
- tabNode.config.getOrElse(SemanticsProperties.Selected) { false }
- )
- if (itemInfo != null) {
- info.setCollectionItemInfo(itemInfo)
- }
- }
- }
- }
- }
-
- /** A naïve algorithm to determine if elements are stacked vertically or horizontally */
- private fun calculateIfHorizontallyStacked(items: List<SemanticsNode>): Boolean {
- if (items.count() < 2) return true
-
- val deltas = items.fastZipWithNext { el1, el2 ->
- Offset(
- abs(el1.boundsInRoot.center.x - el2.boundsInRoot.center.x),
- abs(el1.boundsInRoot.center.y - el2.boundsInRoot.center.y)
- )
- }
- val (deltaX, deltaY) = when (deltas.count()) {
- 1 -> deltas.first()
- else -> deltas.fastReduce { result, element -> result + element }
- }
- return deltaY < deltaX
- }
-
- private fun getSelectionMode(items: List<SemanticsNode>): Int {
- var numberOfSelectedItems = 0
- items.fastForEach {
- if (it.config.getOrElse(SemanticsProperties.Selected) { false }) {
- numberOfSelectedItems += 1
- }
- }
- return when (numberOfSelectedItems) {
- 0 -> AccessibilityNodeInfoCompat.CollectionInfoCompat.SELECTION_MODE_NONE
- 1 -> AccessibilityNodeInfoCompat.CollectionInfoCompat.SELECTION_MODE_SINGLE
- else -> AccessibilityNodeInfoCompat.CollectionInfoCompat.SELECTION_MODE_MULTIPLE
- }
- }
-
// TODO(b/160820721): use AccessibilityNodeProviderCompat instead of AccessibilityNodeProvider
inner class MyNodeProvider : AccessibilityNodeProvider() {
override fun createAccessibilityNodeInfo(virtualViewId: Int):
diff --git a/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/platform/accessibility/CollectionInfo.kt b/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/platform/accessibility/CollectionInfo.kt
new file mode 100644
index 0000000..7bfbdb9
--- /dev/null
+++ b/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/platform/accessibility/CollectionInfo.kt
@@ -0,0 +1,150 @@
+/*
+ * Copyright 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.compose.ui.platform.accessibility
+
+import androidx.compose.ui.fastReduce
+import androidx.compose.ui.fastZipWithNext
+import androidx.compose.ui.geometry.Offset
+import androidx.compose.ui.semantics.CollectionInfo
+import androidx.compose.ui.semantics.CollectionItemInfo
+import androidx.compose.ui.semantics.SemanticsNode
+import androidx.compose.ui.semantics.SemanticsProperties
+import androidx.compose.ui.semantics.getOrNull
+import androidx.compose.ui.util.fastForEach
+import androidx.compose.ui.util.fastForEachIndexed
+import androidx.core.view.accessibility.AccessibilityNodeInfoCompat
+import kotlin.math.abs
+
+internal fun setCollectionInfo(node: SemanticsNode, info: AccessibilityNodeInfoCompat) {
+ // prioritise collection info provided by developer
+ val collectionInfo = node.config.getOrNull(SemanticsProperties.CollectionInfo)
+ if (collectionInfo != null) {
+ info.setCollectionInfo(collectionInfo.toAccessibilityCollectionInfo())
+ return
+ }
+
+ // if no collection info is provided, we'll check the 'SelectableGroup'
+ val groupedChildren = mutableListOf<SemanticsNode>()
+
+ if (node.config.getOrNull(SemanticsProperties.SelectableGroup) != null) {
+ node.children.fastForEach { childNode ->
+ // we assume that Tabs and RadioButtons are not mixed under a single group
+ if (childNode.config.contains(SemanticsProperties.Selected)) {
+ groupedChildren.add(childNode)
+ }
+ }
+ }
+
+ if (groupedChildren.isNotEmpty()) {
+ val isHorizontal = calculateIfHorizontallyStacked(groupedChildren)
+ info.setCollectionInfo(
+ AccessibilityNodeInfoCompat.CollectionInfoCompat.obtain(
+ if (isHorizontal) 1 else groupedChildren.count(),
+ if (isHorizontal) groupedChildren.count() else 1,
+ false,
+ AccessibilityNodeInfoCompat.CollectionInfoCompat.SELECTION_MODE_NONE
+ )
+ )
+ }
+}
+
+internal fun setCollectionItemInfo(node: SemanticsNode, info: AccessibilityNodeInfoCompat) {
+ // prioritise collection item info provided by developer
+ val collectionItemInfo = node.config.getOrNull(SemanticsProperties.CollectionItemInfo)
+ if (collectionItemInfo != null) {
+ info.setCollectionItemInfo(collectionItemInfo.toAccessibilityCollectionItemInfo(node))
+ }
+
+ // if no collection item info is provided, we'll check the 'SelectableGroup'
+ val parentNode = node.parent ?: return
+ if (parentNode.config.getOrNull(SemanticsProperties.SelectableGroup) != null) {
+ // first check if parent has a CollectionInfo. If it does and any of the counters is
+ // unknown, then we assume that it is a lazy collection so we won't provide
+ // collectionItemInfo using `SelectableGroup`
+ val collectionInfo = parentNode.config.getOrNull(SemanticsProperties.CollectionInfo)
+ if (collectionInfo != null && collectionInfo.isLazyCollection) return
+
+ // `SelectableGroup` designed for selectable elements
+ if (!node.config.contains(SemanticsProperties.Selected)) return
+
+ val groupedChildren = mutableListOf<SemanticsNode>()
+
+ // find all siblings to calculate the index
+ parentNode.children.fastForEach { childNode ->
+ if (childNode.config.contains(SemanticsProperties.Selected)) {
+ groupedChildren.add(childNode)
+ }
+ }
+
+ if (groupedChildren.isNotEmpty()) {
+ val isHorizontal = calculateIfHorizontallyStacked(groupedChildren)
+
+ groupedChildren.fastForEachIndexed { index, tabNode ->
+ if (tabNode.id == node.id) {
+ val itemInfo = AccessibilityNodeInfoCompat.CollectionItemInfoCompat.obtain(
+ if (isHorizontal) 0 else index,
+ 1,
+ if (isHorizontal) index else 0,
+ 1,
+ false,
+ tabNode.config.getOrElse(SemanticsProperties.Selected) { false }
+ )
+ if (itemInfo != null) {
+ info.setCollectionItemInfo(itemInfo)
+ }
+ }
+ }
+ }
+ }
+}
+
+/** A naïve algorithm to determine if elements are stacked vertically or horizontally */
+private fun calculateIfHorizontallyStacked(items: List<SemanticsNode>): Boolean {
+ if (items.count() < 2) return true
+
+ val deltas = items.fastZipWithNext { el1, el2 ->
+ Offset(
+ abs(el1.boundsInRoot.center.x - el2.boundsInRoot.center.x),
+ abs(el1.boundsInRoot.center.y - el2.boundsInRoot.center.y)
+ )
+ }
+ val (deltaX, deltaY) = when (deltas.count()) {
+ 1 -> deltas.first()
+ else -> deltas.fastReduce { result, element -> result + element }
+ }
+ return deltaY < deltaX
+}
+
+private val CollectionInfo.isLazyCollection get() = rowCount < 0 || columnCount < 0
+
+private fun CollectionInfo.toAccessibilityCollectionInfo() =
+ AccessibilityNodeInfoCompat.CollectionInfoCompat.obtain(
+ rowCount,
+ columnCount,
+ false,
+ AccessibilityNodeInfoCompat.CollectionInfoCompat.SELECTION_MODE_NONE
+ )
+
+private fun CollectionItemInfo.toAccessibilityCollectionItemInfo(itemNode: SemanticsNode) =
+ AccessibilityNodeInfoCompat.CollectionItemInfoCompat.obtain(
+ rowIndex,
+ rowSpan,
+ columnIndex,
+ columnSpan,
+ false,
+ itemNode.config.getOrElse(SemanticsProperties.Selected) { false }
+ )
\ No newline at end of file
diff --git a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/semantics/SemanticsProperties.kt b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/semantics/SemanticsProperties.kt
index 81ae47e..d99109b 100644
--- a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/semantics/SemanticsProperties.kt
+++ b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/semantics/SemanticsProperties.kt
@@ -67,6 +67,12 @@
/** @see SemanticsPropertyReceiver.selectableGroup */
val SelectableGroup = SemanticsPropertyKey<Unit>("SelectableGroup")
+ /** @see SemanticsPropertyReceiver.collectionInfo */
+ val CollectionInfo = SemanticsPropertyKey<CollectionInfo>("CollectionInfo")
+
+ /** @see SemanticsPropertyReceiver.collectionItemInfo */
+ val CollectionItemInfo = SemanticsPropertyKey<CollectionItemInfo>("CollectionItemInfo")
+
/**
* @see SemanticsPropertyReceiver.heading
*/
@@ -481,6 +487,39 @@
}
/**
+ * Information about the collection.
+ *
+ * A collection of items has [rowCount] rows and [columnCount] columns.
+ * For example, a vertical list is a collection with one column, as many rows as the list items
+ * that are important for accessibility; A table is a collection with several rows and several
+ * columns.
+ *
+ * @param rowCount the number of rows in the collection, or -1 if unknown
+ * @param columnCount the number of columns in the collection, or -1 if unknown
+ */
+class CollectionInfo(val rowCount: Int, val columnCount: Int)
+
+/**
+ * Information about the item of a collection.
+ *
+ * A collection item is contained in a collection, it starts at a given [rowIndex] and
+ * [columnIndex] in the collection, and spans one or more rows and columns. For example, a header
+ * of two related table columns starts at the first row and the first column, spans one row and
+ * two columns.
+ *
+ * @param rowIndex the index of the row at which item is located
+ * @param rowSpan the number of rows the item spans
+ * @param columnIndex the index of the column at which item is located
+ * @param columnSpan the number of columns the item spans
+ */
+class CollectionItemInfo(
+ val rowIndex: Int,
+ val rowSpan: Int,
+ val columnIndex: Int,
+ val columnSpan: Int
+)
+
+/**
* The scroll state of one axis if this node is scrollable.
*
* @param value current 0-based scroll position value (either in pixels, or lazy-item count)
@@ -693,8 +732,6 @@
* properties of this element. But some elements with subtle differences need an exact role. If
* an exact role is not listed in [Role], this property should not be set and the framework will
* automatically resolve it.
- *
- * @see SemanticsProperties.Role
*/
var SemanticsPropertyReceiver.role by SemanticsProperties.Role
@@ -737,6 +774,21 @@
var SemanticsPropertyReceiver.selected by SemanticsProperties.Selected
/**
+ * This semantics marks node as a collection and provides the required information.
+ *
+ * @see collectionItemInfo
+ */
+var SemanticsPropertyReceiver.collectionInfo by SemanticsProperties.CollectionInfo
+
+/**
+ * This semantics marks node as an items of a collection and provides the required information.
+ *
+ * If you mark items of a collection, you should also be marking the collection with
+ * [collectionInfo].
+ */
+var SemanticsPropertyReceiver.collectionItemInfo by SemanticsProperties.CollectionItemInfo
+
+/**
* The state of a toggleable component.
*
* The presence of this property indicates that the element is toggleable.
@@ -771,6 +823,11 @@
/**
* The node is marked as a collection of horizontally or vertically stacked selectable elements.
*
+ * Unlike [collectionInfo] which marks a collection of any elements and asks developer to
+ * provide all the required information like number of elements etc., this semantics will
+ * populate the number of selectable elements automatically. Note that if you use this semantics
+ * with lazy collections, it won't get the number of elements in the collection.
+ *
* @see SemanticsPropertyReceiver.selected
*/
fun SemanticsPropertyReceiver.selectableGroup() {
diff --git a/fragment/fragment/api/public_plus_experimental_current.txt b/fragment/fragment/api/public_plus_experimental_current.txt
index adc45fa..c4a2d4d 100644
--- a/fragment/fragment/api/public_plus_experimental_current.txt
+++ b/fragment/fragment/api/public_plus_experimental_current.txt
@@ -276,7 +276,6 @@
method public final void clearFragmentResultListener(String);
method public void dump(String, java.io.FileDescriptor?, java.io.PrintWriter, String![]?);
method @Deprecated public static void enableDebugLogging(boolean);
- method @androidx.fragment.app.FragmentStateManagerControl public static void enableNewStateManager(boolean);
method public boolean executePendingTransactions();
method public static <F extends androidx.fragment.app.Fragment> F findFragment(android.view.View);
method public androidx.fragment.app.Fragment? findFragmentById(@IdRes int);
@@ -370,9 +369,6 @@
method public void setFragmentResultListener(String, androidx.lifecycle.LifecycleOwner, androidx.fragment.app.FragmentResultListener);
}
- @experimental.Experimental(level=androidx.annotation.experimental.Experimental.Level.WARNING) @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) @java.lang.annotation.Target({java.lang.annotation.ElementType.METHOD}) public @interface FragmentStateManagerControl {
- }
-
@Deprecated public abstract class FragmentStatePagerAdapter extends androidx.viewpager.widget.PagerAdapter {
ctor @Deprecated public FragmentStatePagerAdapter(androidx.fragment.app.FragmentManager);
ctor @Deprecated public FragmentStatePagerAdapter(androidx.fragment.app.FragmentManager, int);
diff --git a/fragment/fragment/src/androidTest/java/androidx/fragment/app/FragmentReorderingTest.kt b/fragment/fragment/src/androidTest/java/androidx/fragment/app/FragmentReorderingTest.kt
index 85860a6..fde5c4d 100644
--- a/fragment/fragment/src/androidTest/java/androidx/fragment/app/FragmentReorderingTest.kt
+++ b/fragment/fragment/src/androidTest/java/androidx/fragment/app/FragmentReorderingTest.kt
@@ -23,26 +23,19 @@
import androidx.fragment.test.R
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.LifecycleEventObserver
+import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import androidx.test.platform.app.InstrumentationRegistry
import com.google.common.truth.Truth.assertThat
import com.google.common.truth.Truth.assertWithMessage
-import org.junit.After
import org.junit.Before
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
-import org.junit.runners.Parameterized
@SmallTest
-@RunWith(Parameterized::class)
-class FragmentReorderingTest(private val stateManager: StateManager) {
-
- companion object {
- @JvmStatic
- @Parameterized.Parameters(name = "stateManager={0}")
- fun data() = arrayOf(NewStateManager, OldStateManager)
- }
+@RunWith(AndroidJUnit4::class)
+class FragmentReorderingTest() {
@Suppress("DEPRECATION")
@get:Rule
@@ -54,25 +47,16 @@
@Before
fun setup() {
- stateManager.setup()
activityRule.setContentView(R.layout.simple_container)
container = activityRule.activity.findViewById<View>(R.id.fragmentContainer) as ViewGroup
fm = activityRule.activity.supportFragmentManager
instrumentation = InstrumentationRegistry.getInstrumentation()
}
- @After
- fun teardown() {
- stateManager.teardown()
- }
-
// Ensure that a replaced fragment is stopped before its replacement is started
// and vice versa when popped
@Test
fun stopBeforeStart() {
- if (stateManager is OldStateManager) {
- return
- }
val fragment1 = StrictViewFragment()
fm.beginTransaction()
.add(R.id.fragmentContainer, fragment1)
diff --git a/fragment/fragment/src/androidTest/java/androidx/fragment/app/FragmentTestUtil.kt b/fragment/fragment/src/androidTest/java/androidx/fragment/app/FragmentTestUtil.kt
index 790569f..800e19e 100644
--- a/fragment/fragment/src/androidTest/java/androidx/fragment/app/FragmentTestUtil.kt
+++ b/fragment/fragment/src/androidTest/java/androidx/fragment/app/FragmentTestUtil.kt
@@ -40,27 +40,6 @@
object Reordered : ReorderingAllowed()
object Ordered : ReorderingAllowed()
-sealed class StateManager {
- abstract fun setup()
-
- override fun toString(): String = this.javaClass.simpleName
-
- fun teardown() {
- // Reset it back to the default
- FragmentManager.enableNewStateManager(true)
- }
-}
-object NewStateManager : StateManager() {
- override fun setup() {
- FragmentManager.enableNewStateManager(true)
- }
-}
-object OldStateManager : StateManager() {
- override fun setup() {
- FragmentManager.enableNewStateManager(false)
- }
-}
-
@Suppress("DEPRECATION")
fun androidx.test.rule.ActivityTestRule<out FragmentActivity>.executePendingTransactions(
fm: FragmentManager = activity.supportFragmentManager
diff --git a/fragment/fragment/src/androidTest/java/androidx/fragment/app/FragmentTransitionAnimTest.kt b/fragment/fragment/src/androidTest/java/androidx/fragment/app/FragmentTransitionAnimTest.kt
index ab7bdd1..1ad1ba5 100644
--- a/fragment/fragment/src/androidTest/java/androidx/fragment/app/FragmentTransitionAnimTest.kt
+++ b/fragment/fragment/src/androidTest/java/androidx/fragment/app/FragmentTransitionAnimTest.kt
@@ -31,7 +31,6 @@
import androidx.test.filters.SdkSuppress
import androidx.testutils.withActivity
import com.google.common.truth.Truth.assertThat
-import org.junit.After
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
@@ -44,21 +43,14 @@
@SdkSuppress(minSdkVersion = Build.VERSION_CODES.LOLLIPOP)
class FragmentTransitionAnimTest(
private val reorderingAllowed: ReorderingAllowed,
- private val stateManager: StateManager
) {
private var onBackStackChangedTimes: Int = 0
@Before
fun setup() {
- stateManager.setup()
onBackStackChangedTimes = 0
}
- @After
- fun teardown() {
- stateManager.teardown()
- }
-
// Ensure when transition duration is shorter than animation duration, we will get both end
// callbacks
@Test
@@ -109,13 +101,13 @@
TIMEOUT,
TimeUnit.MILLISECONDS
)
- assertThat(startAnimationRan).isEqualTo(stateManager == OldStateManager)
+ assertThat(startAnimationRan).isFalse()
fragment.waitForTransition()
val exitAnimationRan = fragment.exitAnimationLatch.await(
TIMEOUT,
TimeUnit.MILLISECONDS
)
- assertThat(exitAnimationRan).isEqualTo(stateManager == OldStateManager)
+ assertThat(exitAnimationRan).isFalse()
assertThat(onBackStackChangedTimes).isEqualTo(2)
}
}
@@ -170,13 +162,13 @@
TIMEOUT,
TimeUnit.MILLISECONDS
)
- assertThat(startAnimationRan).isEqualTo(stateManager == OldStateManager)
+ assertThat(startAnimationRan).isFalse()
fragment.waitForTransition()
val exitAnimationRan = fragment.exitAnimationLatch.await(
TIMEOUT,
TimeUnit.MILLISECONDS
)
- assertThat(exitAnimationRan).isEqualTo(stateManager == OldStateManager)
+ assertThat(exitAnimationRan).isFalse()
assertThat(onBackStackChangedTimes).isEqualTo(2)
}
}
@@ -232,7 +224,7 @@
TIMEOUT,
TimeUnit.MILLISECONDS
)
- assertThat(exitAnimatorRan).isEqualTo(stateManager == OldStateManager)
+ assertThat(exitAnimatorRan).isFalse()
assertThat(onBackStackChangedTimes).isEqualTo(2)
}
}
@@ -288,7 +280,7 @@
TIMEOUT,
TimeUnit.MILLISECONDS
)
- assertThat(exitAnimatorRan).isEqualTo(stateManager == OldStateManager)
+ assertThat(exitAnimatorRan).isFalse()
assertThat(onBackStackChangedTimes).isEqualTo(2)
}
}
@@ -342,17 +334,12 @@
companion object {
@JvmStatic
- @Parameterized.Parameters(name = "ordering={0}, stateManager={1}")
+ @Parameterized.Parameters(name = "ordering={0}")
fun data() = mutableListOf<Array<Any>>().apply {
arrayOf(
Ordered,
Reordered
- ).forEach { ordering ->
- // Run the test with the new state manager
- add(arrayOf(ordering, NewStateManager))
- // Run the test with the old state manager
- add(arrayOf(ordering, OldStateManager))
- }
+ )
}
@AnimRes
diff --git a/fragment/fragment/src/androidTest/java/androidx/fragment/app/FragmentTransitionTest.kt b/fragment/fragment/src/androidTest/java/androidx/fragment/app/FragmentTransitionTest.kt
index 4ccfe08..d92c9bf 100644
--- a/fragment/fragment/src/androidTest/java/androidx/fragment/app/FragmentTransitionTest.kt
+++ b/fragment/fragment/src/androidTest/java/androidx/fragment/app/FragmentTransitionTest.kt
@@ -15,7 +15,6 @@
*/
package androidx.fragment.app
-import android.graphics.Rect
import android.os.Build
import android.os.Bundle
import android.transition.Transition
@@ -55,8 +54,7 @@
@RunWith(Parameterized::class)
@SdkSuppress(minSdkVersion = Build.VERSION_CODES.LOLLIPOP)
class FragmentTransitionTest(
- private val reorderingAllowed: ReorderingAllowed,
- private val stateManager: StateManager
+ private val reorderingAllowed: ReorderingAllowed
) {
@Suppress("DEPRECATION")
@@ -71,7 +69,6 @@
@Before
fun setup() {
- stateManager.setup()
activityRule.setContentView(R.layout.simple_container)
onBackStackChangedTimes = 0
fragmentManager = activityRule.activity.supportFragmentManager
@@ -81,7 +78,6 @@
@After
fun teardown() {
fragmentManager.removeOnBackStackChangedListener(onBackStackChangedListener)
- stateManager.teardown()
}
// Test that normal view transitions (enter, exit, reenter, return) run with
@@ -183,8 +179,6 @@
fun removeThenAddBeforeTransitionFinishes() {
// enter transition
val fragment = setupInitialFragment()
- val blue = activityRule.findBlue()
- val green = activityRule.findGreen()
val view1 = fragment.view
@@ -208,23 +202,10 @@
// back stack
if (reorderingAllowed is Reordered) {
assertThat(onBackStackChangedTimes).isEqualTo(2)
- assertThat(fragment.requireView()).isEqualTo(view1)
} else {
assertThat(onBackStackChangedTimes).isEqualTo(3)
- if (stateManager is NewStateManager) {
- // When using FragmentStateManager, the transition gets cancelled and the
- // Fragment does not go all the way through to destroying the view before
- // coming back up, so the view instances will still match
- assertThat(fragment.requireView()).isEqualTo(view1)
- } else {
- // If reorder is not allowed we will get the exit Transition
- fragment.waitForTransition()
- fragment.exitTransition.verifyAndClearTransition {
- exitingViews += listOf(green, blue)
- }
- assertThat(fragment.requireView()).isNotEqualTo(view1)
- }
}
+ assertThat(fragment.requireView()).isEqualTo(view1)
verifyNoOtherTransitions(fragment)
}
@@ -784,9 +765,7 @@
fragment1.waitForTransition()
fragment2.waitForTransition()
fragment1.exitTransition.endAnimatorCountDownLatch.await(1000, TimeUnit.MILLISECONDS)
- if (stateManager is NewStateManager) {
- assertThat(listener.isGoneAtTransitionStart).isFalse()
- }
+ assertThat(listener.isGoneAtTransitionStart).isFalse()
val endGreen = findViewById(fragment2, R.id.greenSquare)
val endBlue = findViewById(fragment2, R.id.blueSquare)
@@ -946,7 +925,6 @@
val startBlue = activityRule.findBlue()
val startGreen = activityRule.findGreen()
- val startBlueBounds = startBlue.boundsOnScreen
fragmentManager.beginTransaction()
.addSharedElement(startBlue, "fooSquare")
@@ -962,22 +940,10 @@
val endBlue = activityRule.findBlue()
val endGreen = activityRule.findGreen()
- // FragmentStateManager is able to build the correct transition
- // whether you use reordering or not
- if (stateManager is NewStateManager || reorderingAllowed is Reordered) {
- fragment1.exitTransition.verifyAndClearTransition {
- exitingViews += listOf(startGreen, startBlue)
- }
- } else {
- fragment1.exitTransition.verifyAndClearTransition {
- epicenter = startBlueBounds
- exitingViews += startGreen
- }
- fragment2.sharedElementEnter.verifyAndClearTransition {
- epicenter = startBlueBounds
- exitingViews += startBlue
- }
+ fragment1.exitTransition.verifyAndClearTransition {
+ exitingViews += listOf(startGreen, startBlue)
}
+
verifyNoOtherTransitions(fragment1)
fragment2.enterTransition.verifyAndClearTransition {
@@ -1116,16 +1082,10 @@
if (reorderingAllowed is Reordered) {
// reordering allowed fragment3 to get a transition so we should wait for it to finish
fragment3.waitForTransition()
- if (stateManager is NewStateManager) {
- // When using the NewStateManager, the last operation sets the direction.
- // In this case, the forward direction since we did a replace() after the pop
- fragment2.exitTransition.verifyAndClearTransition {
- exitingViews += listOf(midGreen, midBlue)
- }
- } else {
- fragment2.returnTransition.verifyAndClearTransition {
- exitingViews += listOf(midGreen, midBlue)
- }
+ // The last operation (in this case a replace()) sets the direction of
+ // the transition, so the popped fragment runs its exit transition
+ fragment2.exitTransition.verifyAndClearTransition {
+ exitingViews += listOf(midGreen, midBlue)
}
val endGreen = activityRule.findGreen()
val endBlue = activityRule.findBlue()
@@ -1181,29 +1141,18 @@
val endBlue = activityRule.findBlue()
val endGreenBounds = endGreen.boundsOnScreen
- if (stateManager is NewStateManager) {
- // When using the NewStateManager, the last operation sets the direction.
- // In this case, the forward direction since we did a replace() after the pop
- fragment1.exitTransition.verifyAndClearTransition {
- epicenter = endGreenBounds
- exitingViews += startGreen
- }
- } else {
- fragment1.returnTransition.verifyAndClearTransition {
- exitingViews += startGreen
- }
+ // The last operation (in this case a replace()) sets the direction of
+ // the transition, so the popped fragment runs its exit transition
+ fragment1.exitTransition.verifyAndClearTransition {
+ epicenter = endGreenBounds
+ exitingViews += startGreen
}
fragment2.enterTransition.verifyAndClearTransition {
epicenter = startGreenBounds
enteringViews += endGreen
}
fragment2.sharedElementEnter.verifyAndClearTransition {
- epicenter = if (stateManager is NewStateManager) {
- endGreenBounds
- } else {
- // In this case, we can't find an epicenter
- Rect()
- }
+ epicenter = endGreenBounds
exitingViews += startBlue
enteringViews += endBlue
}
@@ -1242,20 +1191,10 @@
val midRed = activityRule.findRed()
val midGreenBounds = midGreen.boundsOnScreen
- // FragmentStateManager is able to build the correct transition
- // whether you use reordering or not
- if (stateManager is NewStateManager || reorderingAllowed is Reordered) {
- fragment2.sharedElementEnter.verifyAndClearTransition {
- epicenter = startGreenBounds
- exitingViews += startGreen
- enteringViews += midGreen
- }
- } else {
- fragment2.sharedElementEnter.verifyAndClearTransition {
- epicenter = startGreenBounds
- exitingViews += listOf(startGreen, startBlue)
- enteringViews += midGreen
- }
+ fragment2.sharedElementEnter.verifyAndClearTransition {
+ epicenter = startGreenBounds
+ exitingViews += startGreen
+ enteringViews += midGreen
}
fragment2.enterTransition.verifyAndClearTransition {
epicenter = midGreenBounds
@@ -1685,18 +1624,8 @@
companion object {
@JvmStatic
- @Parameterized.Parameters(name = "ordering={0}, stateManager={1}")
- fun data() = mutableListOf<Array<Any>>().apply {
- arrayOf(
- Ordered,
- Reordered
- ).forEach { ordering ->
- // Run the test with the new state manager
- add(arrayOf(ordering, NewStateManager))
- // Run the test with the old state manager
- add(arrayOf(ordering, OldStateManager))
- }
- }
+ @Parameterized.Parameters(name = "ordering={0}")
+ fun data() = arrayOf(Ordered, Reordered)
}
}
diff --git a/fragment/fragment/src/androidTest/java/androidx/fragment/app/PostponedTransitionTest.kt b/fragment/fragment/src/androidTest/java/androidx/fragment/app/PostponedTransitionTest.kt
index 0948f8e..157c6c7 100644
--- a/fragment/fragment/src/androidTest/java/androidx/fragment/app/PostponedTransitionTest.kt
+++ b/fragment/fragment/src/androidTest/java/androidx/fragment/app/PostponedTransitionTest.kt
@@ -25,6 +25,7 @@
import androidx.fragment.test.R
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.ViewModelStore
+import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.LargeTest
import androidx.test.filters.SdkSuppress
import androidx.test.platform.app.InstrumentationRegistry
@@ -32,26 +33,17 @@
import androidx.testutils.waitForExecution
import com.google.common.truth.Truth.assertThat
import com.google.common.truth.Truth.assertWithMessage
-import org.junit.After
-import org.junit.Assert.fail
import org.junit.Before
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
-import org.junit.runners.Parameterized
import java.util.concurrent.CountDownLatch
import java.util.concurrent.TimeUnit
@LargeTest
-@RunWith(Parameterized::class)
+@RunWith(AndroidJUnit4::class)
@SdkSuppress(minSdkVersion = Build.VERSION_CODES.LOLLIPOP)
-class PostponedTransitionTest(private val stateManager: StateManager) {
-
- companion object {
- @JvmStatic
- @Parameterized.Parameters(name = "stateManager={0}")
- fun data() = arrayOf(NewStateManager, OldStateManager)
- }
+class PostponedTransitionTest() {
@Suppress("DEPRECATION")
@get:Rule
@@ -62,7 +54,6 @@
@Before
fun setupContainer() {
- stateManager.setup()
activityRule.setContentView(R.layout.simple_container)
val fm = activityRule.activity.supportFragmentManager
@@ -94,11 +85,6 @@
verifyNoOtherTransitions(beginningFragment)
}
- @After
- fun teardown() {
- stateManager.teardown()
- }
-
// Ensure that replacing with a fragment that has a postponed transition
// will properly postpone it, both adding and popping.
@Test
@@ -151,9 +137,6 @@
@Test
fun changePostponedFragmentVisibility() {
- if (stateManager == OldStateManager) {
- return
- }
val fm = activityRule.activity.supportFragmentManager
val startBlue = activityRule.findBlue()
@@ -290,44 +273,20 @@
}
activityRule.waitForExecution()
- if (stateManager is NewStateManager) {
- // fragment2 should have been put on the back stack without any transitions
- verifyNoOtherTransitions(fragment2)
+ // fragment2 should have been put on the back stack without any transitions
+ verifyNoOtherTransitions(fragment2)
- // fragment3 should be postponed
- assertPostponedTransition(beginningFragment, fragment3)
+ // fragment3 should be postponed
+ assertPostponedTransition(beginningFragment, fragment3)
- // start the postponed transition
- fragment3.startPostponedEnterTransition()
+ // start the postponed transition
+ fragment3.startPostponedEnterTransition()
- // make sure it ran
- assertForwardTransition(
- startBlue, startBlueBounds, startGreen,
- beginningFragment, fragment3
- )
- } else {
- // transition to fragment2 should be started
- assertForwardTransition(
- startBlue, startBlueBounds, startGreen,
- beginningFragment, fragment2
- )
-
- // fragment3 should be postponed, but fragment2 should be executed with no transition.
- assertPostponedTransition(fragment2, fragment3, beginningFragment)
-
- val startBlue2 = fragment2.requireView().findViewById<View>(R.id.blueSquare)
- val startGreen2 = fragment2.requireView().findViewById<View>(R.id.greenSquare)
- val startBlueBounds2 = startBlue2.boundsOnScreen
-
- // start the postponed transition
- fragment3.startPostponedEnterTransition()
-
- // make sure it ran
- assertForwardTransition(
- startBlue2, startBlueBounds2, startGreen2,
- fragment2, fragment3
- )
- }
+ // make sure it ran
+ assertForwardTransition(
+ startBlue, startBlueBounds, startGreen,
+ beginningFragment, fragment3
+ )
val startBlue3 = fragment3.requireView().findViewById<View>(R.id.blueSquare)
val startGreen3 = fragment3.requireView().findViewById<View>(R.id.greenSquare)
@@ -335,37 +294,18 @@
activityRule.popBackStackImmediate(commit, FragmentManager.POP_BACK_STACK_INCLUSIVE)
- if (stateManager is NewStateManager) {
- // The transition back to beginningFragment should be postponed
- // and fragment2 should be removed without any transitions
- assertPostponedTransition(fragment3, beginningFragment, fragment2)
+ // The transition back to beginningFragment should be postponed
+ // and fragment2 should be removed without any transitions
+ assertPostponedTransition(fragment3, beginningFragment, fragment2)
- // start the postponed transition
- beginningFragment.startPostponedEnterTransition()
+ // start the postponed transition
+ beginningFragment.startPostponedEnterTransition()
- // make sure it ran
- assertBackTransition(
- startBlue3, startBlueBounds3, startGreen3,
- fragment3, beginningFragment
- )
- } else {
- assertBackTransition(startBlue3, startBlueBounds3, startGreen3, fragment3, fragment2)
-
- assertPostponedTransition(fragment2, beginningFragment, fragment3)
-
- val endBlue2 = fragment2.requireView().findViewById<View>(R.id.blueSquare)
- val endGreen2 = fragment2.requireView().findViewById<View>(R.id.greenSquare)
- val endBlueBounds2 = endBlue2.boundsOnScreen
-
- // start the postponed transition
- beginningFragment.startPostponedEnterTransition()
-
- // make sure it ran
- assertBackTransition(
- endBlue2, endBlueBounds2, endGreen2,
- fragment2, beginningFragment
- )
- }
+ // make sure it ran
+ assertBackTransition(
+ startBlue3, startBlueBounds3, startGreen3,
+ fragment3, beginningFragment
+ )
}
// Ensure that postponed transition is forced after another has been committed.
@@ -402,35 +342,20 @@
// and start fragment2 -> fragment3 transition postponed
activityRule.waitForExecution()
- if (stateManager is NewStateManager) {
- // fragment2 should have been put on the back stack without any transitions
- verifyNoOtherTransitions(fragment2)
+ // fragment2 should have been put on the back stack without any transitions
+ verifyNoOtherTransitions(fragment2)
- // fragment3 should be postponed
- assertPostponedTransition(beginningFragment, fragment3)
+ // fragment3 should be postponed
+ assertPostponedTransition(beginningFragment, fragment3)
- // start the postponed transition
- fragment3.startPostponedEnterTransition()
+ // start the postponed transition
+ fragment3.startPostponedEnterTransition()
- // make sure it ran
- assertForwardTransition(
- startBlue, startBlueBounds, startGreen,
- beginningFragment, fragment3
- )
- } else {
- // fragment3 should be postponed, but fragment2 should be executed with no transition.
- assertPostponedTransition(fragment2, fragment3, beginningFragment)
-
- val startBlue2 = activityRule.findBlue()
- val startGreen2 = activityRule.findGreen()
- val startBlueBounds2 = startBlue2.boundsOnScreen
-
- // start the postponed transition
- fragment3.startPostponedEnterTransition()
-
- // make sure it ran
- assertForwardTransition(startBlue2, startBlueBounds2, startGreen2, fragment2, fragment3)
- }
+ // make sure it ran
+ assertForwardTransition(
+ startBlue, startBlueBounds, startGreen,
+ beginningFragment, fragment3
+ )
val startBlue3 = fragment3.requireView().findViewById<View>(R.id.blueSquare)
val startGreen3 = fragment3.requireView().findViewById<View>(R.id.greenSquare)
@@ -445,37 +370,20 @@
// start the beginningFragment transaction postponed
activityRule.popBackStackImmediate()
- if (stateManager is NewStateManager) {
- // The transition back to beginningFragment should be postponed
- // and no transitions should be done on fragment2
- assertPostponedTransition(fragment3, beginningFragment)
- verifyNoOtherTransitions(fragment2)
+ // The transition back to beginningFragment should be postponed
+ // and no transitions should be done on fragment2
+ assertPostponedTransition(fragment3, beginningFragment)
+ verifyNoOtherTransitions(fragment2)
- // start the postponed transition
- beginningFragment.startPostponedEnterTransition()
+ // start the postponed transition
+ beginningFragment.startPostponedEnterTransition()
- // make sure it ran
- assertBackTransition(
- startBlue3, startBlueBounds3, startGreen3,
- fragment3, beginningFragment
- )
- verifyNoOtherTransitions(fragment2)
- } else {
- assertPostponedTransition(fragment2, beginningFragment, fragment3)
-
- val endBlue2 = fragment2.requireView().findViewById<View>(R.id.blueSquare)
- val endGreen2 = fragment2.requireView().findViewById<View>(R.id.greenSquare)
- val endBlueBounds2 = endBlue2.boundsOnScreen
-
- // start the postponed transition
- beginningFragment.startPostponedEnterTransition()
-
- // make sure it ran
- assertBackTransition(
- endBlue2, endBlueBounds2, endGreen2,
- fragment2, beginningFragment
- )
- }
+ // make sure it ran
+ assertBackTransition(
+ startBlue3, startBlueBounds3, startGreen3,
+ fragment3, beginningFragment
+ )
+ verifyNoOtherTransitions(fragment2)
}
// Do a bunch of things to one fragment in a transaction and see if it can screw things up.
@@ -852,8 +760,6 @@
activityRule.waitForExecution()
val startBlue2 = fragment2.requireView().findViewById<View>(R.id.blueSquare)
- val startGreen2 = fragment2.requireView().findViewById<View>(R.id.greenSquare)
- val startBlueBounds2 = startBlue2.boundsOnScreen
instrumentation.runOnMainSync {
fm.beginTransaction()
@@ -863,31 +769,19 @@
.commitNow()
}
- if (stateManager is NewStateManager) {
- // fragment2 should have been put on the back stack without any transitions
- verifyNoOtherTransitions(fragment2)
+ // fragment2 should have been put on the back stack without any transitions
+ verifyNoOtherTransitions(fragment2)
- // fragment3 should be postponed
- assertPostponedTransition(beginningFragment, fragment3)
+ // fragment3 should be postponed
+ assertPostponedTransition(beginningFragment, fragment3)
- // start the postponed transition
- fragment3.startPostponedEnterTransition()
+ // start the postponed transition
+ fragment3.startPostponedEnterTransition()
- assertForwardTransition(
- startBlue, startBlueBounds, startGreen,
- beginningFragment, fragment3
- )
- } else {
- assertPostponedTransition(fragment2, fragment3, beginningFragment)
-
- // start the postponed transition
- fragment3.startPostponedEnterTransition()
-
- assertForwardTransition(
- startBlue2, startBlueBounds2, startGreen2,
- fragment2, fragment3
- )
- }
+ assertForwardTransition(
+ startBlue, startBlueBounds, startGreen,
+ beginningFragment, fragment3
+ )
}
// Make sure that when a transaction that removes a view is postponed that
@@ -1034,25 +928,9 @@
activityRule.runOnUiThread {
// start the postponed transition
fragment.startPostponedEnterTransition()
-
- if (stateManager is NewStateManager) {
- // This should succeed since onResume() is called outside of the
- // transaction completing
- fm.executePendingTransactions()
- } else {
- try {
- // This should trigger an IllegalStateException
- fm.executePendingTransactions()
- fail(
- "commitNow() while executing a transaction should cause an" +
- " IllegalStateException"
- )
- } catch (e: IllegalStateException) {
- assertThat(e)
- .hasMessageThat()
- .contains("FragmentManager is already executing transactions")
- }
- }
+ // This should succeed since onResume() is called outside of the
+ // transaction completing
+ fm.executePendingTransactions()
}
}
@@ -1241,13 +1119,8 @@
verifyNoOtherTransitions(fromFragment)
verifyNoOtherTransitions(toFragment)
- if (stateManager is NewStateManager) {
- assertThat(fromFragment.lifecycle.currentState).isEqualTo(Lifecycle.State.CREATED)
- assertThat(toFragment.lifecycle.currentState).isEqualTo(Lifecycle.State.STARTED)
- } else {
- assertThat(fromFragment.isResumed).isTrue()
- assertThat(toFragment.isResumed).isFalse()
- }
+ assertThat(fromFragment.lifecycle.currentState).isEqualTo(Lifecycle.State.CREATED)
+ assertThat(toFragment.lifecycle.currentState).isEqualTo(Lifecycle.State.STARTED)
}
private fun clearTargets(fragment: TransitionFragment) {
diff --git a/fragment/fragment/src/androidTest/java/androidx/fragment/app/PrimaryNavFragmentTest.kt b/fragment/fragment/src/androidTest/java/androidx/fragment/app/PrimaryNavFragmentTest.kt
index 8bd7499..4255529 100644
--- a/fragment/fragment/src/androidTest/java/androidx/fragment/app/PrimaryNavFragmentTest.kt
+++ b/fragment/fragment/src/androidTest/java/androidx/fragment/app/PrimaryNavFragmentTest.kt
@@ -300,29 +300,11 @@
activityRule.waitForExecution()
assertThat(navigations.drain()).isEqualTo(
- if (FragmentManager.USE_STATE_MANAGER) {
- listOf(
- trackingFragment to false,
- postponedFragment to true
- )
- } else {
- listOf(
- trackingFragment to false,
- postponedFragment to true,
- postponedFragment to false,
- trackingFragment to true
- )
- }
+ listOf(trackingFragment to false, postponedFragment to true)
)
assertWithMessage("primary nav fragment not set correctly after replace")
.that(fm.primaryNavigationFragment)
- .isSameInstanceAs(
- if (FragmentManager.USE_STATE_MANAGER) {
- postponedFragment
- } else {
- trackingFragment
- }
- )
+ .isSameInstanceAs(postponedFragment)
// Now pop the back stack and also add a replacement Fragment
fm.popBackStack()
@@ -335,23 +317,12 @@
activityRule.waitForExecution()
assertThat(navigations.drain()).isEqualTo(
- if (FragmentManager.USE_STATE_MANAGER) {
- listOf(
- postponedFragment to false,
- trackingFragment to true,
- trackingFragment to false,
- replacementFragment to true
- )
- } else {
- listOf(
- trackingFragment to false,
- postponedFragment to true,
- postponedFragment to false,
- trackingFragment to true,
- trackingFragment to false,
- replacementFragment to true
- )
- }
+ listOf(
+ postponedFragment to false,
+ trackingFragment to true,
+ trackingFragment to false,
+ replacementFragment to true
+ )
)
assertWithMessage("primary nav fragment not set correctly after replace")
diff --git a/fragment/fragment/src/androidTest/java/androidx/fragment/app/SpecialEffectsControllerTest.kt b/fragment/fragment/src/androidTest/java/androidx/fragment/app/SpecialEffectsControllerTest.kt
index 6fa9e48..bdda9ab 100644
--- a/fragment/fragment/src/androidTest/java/androidx/fragment/app/SpecialEffectsControllerTest.kt
+++ b/fragment/fragment/src/androidTest/java/androidx/fragment/app/SpecialEffectsControllerTest.kt
@@ -16,7 +16,6 @@
package androidx.fragment.app
-import android.view.View
import android.view.ViewGroup
import android.widget.FrameLayout
import androidx.fragment.app.test.EmptyFragmentTestActivity
@@ -417,11 +416,6 @@
// This moves the Fragment up to STARTED,
// calling enqueueAdd() under the hood
fragmentStateManager.moveToExpectedState()
- // Normally this would be done for us in the USE_STATE_MANAGER world
- // but we need to do it manually if that isn't the case yet.
- if (!FragmentManager.USE_STATE_MANAGER) {
- fragment.mView.visibility = View.INVISIBLE
- }
}
assertThat(controller.operationsToExecute)
.isEmpty()
@@ -442,17 +436,8 @@
onActivity {
fragment.startPostponedEnterTransition()
}
- onActivity {
- // When USE_STATE_MANAGER is true, this second onActivity is enough
- // to handle the post() that startPostponedEnterTransition() does.
- // Otherwise, we need to do a little more work at this point
- if (!FragmentManager.USE_STATE_MANAGER) {
- // These are called automatically when USE_STATE_MANAGER is true
- // but we need to call them manually if it is false
- controller.markPostponedState()
- controller.executePendingOperations()
- }
- }
+ // Wait for idle thread to handle the post() that startPostponedEnterTransition() does.
+ InstrumentationRegistry.getInstrumentation().waitForIdleSync()
// Verify that the operation was sent for execution
assertThat(controller.operationsToExecute)
diff --git a/fragment/fragment/src/androidTest/java/androidx/fragment/app/StrictViewFragment.kt b/fragment/fragment/src/androidTest/java/androidx/fragment/app/StrictViewFragment.kt
index 21201eb..00b0c2e 100644
--- a/fragment/fragment/src/androidTest/java/androidx/fragment/app/StrictViewFragment.kt
+++ b/fragment/fragment/src/androidTest/java/androidx/fragment/app/StrictViewFragment.kt
@@ -73,10 +73,7 @@
assertWithMessage("getView returned null in onDestroyView")
.that(view)
.isNotNull()
- if (requireView().parent != null &&
- requireView().animation != null &&
- FragmentManager.USE_STATE_MANAGER
- ) {
+ if (requireView().parent != null && requireView().animation != null) {
assertWithMessage("View should be removed from parent if there is no animation")
.that((requireView().parent as ViewGroup).layoutTransition)
.isNotNull()
diff --git a/fragment/fragment/src/main/java/androidx/fragment/app/BackStackRecord.java b/fragment/fragment/src/main/java/androidx/fragment/app/BackStackRecord.java
index c67ee5a..e691044 100644
--- a/fragment/fragment/src/main/java/androidx/fragment/app/BackStackRecord.java
+++ b/fragment/fragment/src/main/java/androidx/fragment/app/BackStackRecord.java
@@ -364,49 +364,8 @@
return true;
}
- boolean interactsWith(int containerId) {
- final int numOps = mOps.size();
- for (int opNum = 0; opNum < numOps; opNum++) {
- final Op op = mOps.get(opNum);
- final int fragContainer = op.mFragment != null ? op.mFragment.mContainerId : 0;
- if (fragContainer != 0 && fragContainer == containerId) {
- return true;
- }
- }
- return false;
- }
-
- boolean interactsWith(ArrayList<BackStackRecord> records, int startIndex, int endIndex) {
- if (endIndex == startIndex) {
- return false;
- }
- final int numOps = mOps.size();
- int lastContainer = -1;
- for (int opNum = 0; opNum < numOps; opNum++) {
- final Op op = mOps.get(opNum);
- final int container = op.mFragment != null ? op.mFragment.mContainerId : 0;
- if (container != 0 && container != lastContainer) {
- lastContainer = container;
- for (int i = startIndex; i < endIndex; i++) {
- BackStackRecord record = records.get(i);
- final int numThoseOps = record.mOps.size();
- for (int thoseOpIndex = 0; thoseOpIndex < numThoseOps; thoseOpIndex++) {
- final Op thatOp = record.mOps.get(thoseOpIndex);
- final int thatContainer = thatOp.mFragment != null
- ? thatOp.mFragment.mContainerId : 0;
- if (thatContainer == container) {
- return true;
- }
- }
- }
- }
- }
- return false;
- }
-
/**
- * Executes the operations contained within this transaction. The Fragment states will only
- * be modified if optimizations are not allowed.
+ * Executes the operations contained within this transaction.
*/
void executeOps() {
final int numOps = mOps.size();
@@ -459,26 +418,13 @@
default:
throw new IllegalArgumentException("Unknown cmd: " + op.mCmd);
}
- if (!mReorderingAllowed && op.mCmd != OP_ADD && f != null) {
- if (!FragmentManager.USE_STATE_MANAGER) {
- mManager.moveFragmentToExpectedState(f);
- }
- }
- }
- if (!mReorderingAllowed && !FragmentManager.USE_STATE_MANAGER) {
- // Added fragments are added at the end to comply with prior behavior.
- mManager.moveToState(mManager.mCurState, true);
}
}
/**
- * Reverses the execution of the operations within this transaction. The Fragment states will
- * only be modified if reordering is not allowed.
- *
- * @param moveToState {@code true} if added fragments should be moved to their final state
- * in ordered transactions
+ * Reverses the execution of the operations within this transaction.
*/
- void executePopOps(boolean moveToState) {
+ void executePopOps() {
for (int opNum = mOps.size() - 1; opNum >= 0; opNum--) {
final Op op = mOps.get(opNum);
Fragment f = op.mFragment;
@@ -529,14 +475,6 @@
default:
throw new IllegalArgumentException("Unknown cmd: " + op.mCmd);
}
- if (!mReorderingAllowed && op.mCmd != OP_REMOVE && f != null) {
- if (!FragmentManager.USE_STATE_MANAGER) {
- mManager.moveFragmentToExpectedState(f);
- }
- }
- }
- if (!mReorderingAllowed && moveToState && !FragmentManager.USE_STATE_MANAGER) {
- mManager.moveToState(mManager.mCurState, true);
}
}
@@ -708,31 +646,6 @@
}
}
- boolean isPostponed() {
- for (int opNum = 0; opNum < mOps.size(); opNum++) {
- final Op op = mOps.get(opNum);
- if (isFragmentPostponed(op)) {
- return true;
- }
- }
- return false;
- }
-
- void setOnStartPostponedListener(Fragment.OnStartEnterTransitionListener listener) {
- for (int opNum = 0; opNum < mOps.size(); opNum++) {
- final Op op = mOps.get(opNum);
- if (isFragmentPostponed(op)) {
- op.mFragment.setOnStartEnterTransitionListener(listener);
- }
- }
- }
-
- private static boolean isFragmentPostponed(Op op) {
- final Fragment fragment = op.mFragment;
- return fragment != null && fragment.mAdded && fragment.mView != null && !fragment.mDetached
- && !fragment.mHidden && fragment.isPostponed();
- }
-
@Override
@Nullable
public String getName() {
diff --git a/fragment/fragment/src/main/java/androidx/fragment/app/Fragment.java b/fragment/fragment/src/main/java/androidx/fragment/app/Fragment.java
index 6a65314..c33fffe 100644
--- a/fragment/fragment/src/main/java/androidx/fragment/app/Fragment.java
+++ b/fragment/fragment/src/main/java/androidx/fragment/app/Fragment.java
@@ -263,19 +263,9 @@
}
};
- // True if the View was added, and its animation has yet to be run. This could
- // also indicate that the fragment view hasn't been made visible, even if there is no
- // animation for this fragment.
- boolean mIsNewlyAdded;
-
// True if mHidden has been changed and the animation should be scheduled.
boolean mHiddenChanged;
- // The alpha of the view when the view was added and then postponed. If the value is less
- // than zero, this means that the view's add was canceled and should not participate in
- // removal animations.
- float mPostponedAlpha;
-
// The cached value from onGetLayoutInflater(Bundle) that will be returned from
// getLayoutInflater()
LayoutInflater mLayoutInflater;
@@ -1145,16 +1135,6 @@
}
/**
- * Return <code>true</code> if this fragment or any of its ancestors are currently being removed
- * from its activity. This is <em>not</em> whether its activity is finishing, but rather
- * whether it, or its ancestors are in the process of being removed from its activity.
- */
- final boolean isRemovingParent() {
- Fragment parent = getParentFragment();
- return parent != null && (parent.isRemoving() || parent.isRemovingParent());
- }
-
- /**
* Return true if the layout is included as part of an activity view
* hierarchy via the <fragment> tag. This will always be true when
* fragments are created through the <fragment> tag, <em>except</em>
@@ -2676,10 +2656,7 @@
* independent containers will not interfere with each other's postponement.
* <p>
* Calling postponeEnterTransition on Fragments with a null View will not postpone the
- * transition. Likewise, postponement only works if
- * {@link FragmentTransaction#setReorderingAllowed(boolean) FragmentTransaction reordering} is
- * enabled if you have called {@link FragmentManager#enableNewStateManager(boolean)} with
- * <code>false</code>.
+ * transition.
*
* @see Activity#postponeEnterTransition()
* @see FragmentTransaction#setReorderingAllowed(boolean)
@@ -2709,10 +2686,7 @@
* independent containers will not interfere with each other's postponement.
* <p>
* Calling postponeEnterTransition on Fragments with a null View will not postpone the
- * transition. Likewise, postponement only works if
- * {@link FragmentTransaction#setReorderingAllowed(boolean) FragmentTransaction reordering} is
- * enabled if you have called {@link FragmentManager#enableNewStateManager(boolean)} with
- * <code>false</code>.
+ * transition.
*
* @param duration The length of the delay in {@code timeUnit} units
* @param timeUnit The units of time for {@code duration}
@@ -2768,18 +2742,10 @@
*/
@SuppressWarnings("WeakerAccess") /* synthetic access */
void callStartTransitionListener(boolean calledDirectly) {
- final OnStartEnterTransitionListener listener;
- if (mAnimationInfo == null) {
- listener = null;
- } else {
+ if (mAnimationInfo != null) {
mAnimationInfo.mEnterTransitionPostponed = false;
- listener = mAnimationInfo.mStartEnterTransitionListener;
- mAnimationInfo.mStartEnterTransitionListener = null;
}
- if (listener != null) {
- listener.onStartEnterTransition();
- } else if (FragmentManager.USE_STATE_MANAGER && mView != null
- && mContainer != null && mFragmentManager != null) {
+ if (mView != null && mContainer != null && mFragmentManager != null) {
// Mark the updated postponed state with the SpecialEffectsController immediately
final SpecialEffectsController controller = SpecialEffectsController
.getOrCreateController(mContainer, mFragmentManager);
@@ -3256,23 +3222,6 @@
}
}
- void setOnStartEnterTransitionListener(OnStartEnterTransitionListener listener) {
- ensureAnimationInfo();
- if (listener == mAnimationInfo.mStartEnterTransitionListener) {
- return;
- }
- if (listener != null && mAnimationInfo.mStartEnterTransitionListener != null) {
- throw new IllegalStateException("Trying to set a replacement "
- + "startPostponedEnterTransition on " + this);
- }
- if (mAnimationInfo.mEnterTransitionPostponed) {
- mAnimationInfo.mStartEnterTransitionListener = listener;
- }
- if (listener != null) {
- listener.startListening();
- }
- }
-
private AnimationInfo ensureAnimationInfo() {
if (mAnimationInfo == null) {
mAnimationInfo = new AnimationInfo();
@@ -3391,21 +3340,6 @@
return mAnimationInfo.mAnimatingAway;
}
- void setAnimatingAway(View view) {
- ensureAnimationInfo().mAnimatingAway = view;
- }
-
- void setAnimator(Animator animator) {
- ensureAnimationInfo().mAnimator = animator;
- }
-
- Animator getAnimator() {
- if (mAnimationInfo == null) {
- return null;
- }
- return mAnimationInfo.mAnimator;
- }
-
void setPostOnViewCreatedAlpha(float alpha) {
ensureAnimationInfo().mPostOnViewCreatedAlpha = alpha;
}
@@ -3435,17 +3369,6 @@
return mAnimationInfo.mEnterTransitionPostponed;
}
- boolean isHideReplaced() {
- if (mAnimationInfo == null) {
- return false;
- }
- return mAnimationInfo.mIsHideReplaced;
- }
-
- void setHideReplaced(boolean replaced) {
- ensureAnimationInfo().mIsHideReplaced = replaced;
- }
-
/**
* {@inheritDoc}
*
@@ -3556,16 +3479,6 @@
}
/**
- * Used internally to be notified when {@link #startPostponedEnterTransition()} has
- * been called. This listener will only be called once and then be removed from the
- * listeners.
- */
- interface OnStartEnterTransitionListener {
- void onStartEnterTransition();
- void startListening();
- }
-
- /**
* Contains all the animation and transition information for a fragment. This will only
* be instantiated for Fragments that have Views.
*/
@@ -3575,10 +3488,6 @@
// view that is animating.
View mAnimatingAway;
- // Non-null if the fragment's view hierarchy is currently animating away with an
- // animator instead of an animation.
- Animator mAnimator;
-
// If app requests the animation direction, this is what to use
boolean mIsPop;
@@ -3613,12 +3522,5 @@
// True when postponeEnterTransition has been called and startPostponeEnterTransition
// hasn't been called yet.
boolean mEnterTransitionPostponed;
-
- // Listener to wait for startPostponeEnterTransition. After being called, it will
- // be set to null
- OnStartEnterTransitionListener mStartEnterTransitionListener;
-
- // True if the View was hidden, but the transition is handling the hide
- boolean mIsHideReplaced;
}
}
diff --git a/fragment/fragment/src/main/java/androidx/fragment/app/FragmentAnim.java b/fragment/fragment/src/main/java/androidx/fragment/app/FragmentAnim.java
index 28116cf..aa6272c 100644
--- a/fragment/fragment/src/main/java/androidx/fragment/app/FragmentAnim.java
+++ b/fragment/fragment/src/main/java/androidx/fragment/app/FragmentAnim.java
@@ -18,7 +18,6 @@
import android.animation.Animator;
import android.animation.AnimatorInflater;
-import android.animation.AnimatorListenerAdapter;
import android.content.Context;
import android.content.res.Resources;
import android.content.res.TypedArray;
@@ -31,7 +30,6 @@
import androidx.annotation.AnimRes;
import androidx.annotation.NonNull;
-import androidx.core.os.CancellationSignal;
import androidx.core.view.OneShotPreDrawListener;
import androidx.fragment.R;
@@ -134,83 +132,6 @@
}
}
- /**
- * Animates the removal of a fragment with the given animator or animation. After animating,
- * the fragment's view will be removed from the hierarchy.
- *
- * @param fragment The fragment to animate out
- * @param anim The animator or animation to run on the fragment's view
- */
- static void animateRemoveFragment(@NonNull final Fragment fragment,
- @NonNull AnimationOrAnimator anim,
- @NonNull final FragmentTransition.Callback callback) {
- final View viewToAnimate = fragment.mView;
- final ViewGroup container = fragment.mContainer;
- container.startViewTransition(viewToAnimate);
- final CancellationSignal signal = new CancellationSignal();
- signal.setOnCancelListener(new CancellationSignal.OnCancelListener() {
- @Override
- public void onCancel() {
- if (fragment.getAnimatingAway() != null) {
- View v = fragment.getAnimatingAway();
- fragment.setAnimatingAway(null);
- v.clearAnimation();
- }
- fragment.setAnimator(null);
- }
- });
- callback.onStart(fragment, signal);
- if (anim.animation != null) {
- Animation animation =
- new EndViewTransitionAnimation(anim.animation, container, viewToAnimate);
- fragment.setAnimatingAway(fragment.mView);
- animation.setAnimationListener(new Animation.AnimationListener() {
- @Override
- public void onAnimationStart(Animation animation) {
- }
-
- @Override
- public void onAnimationEnd(Animation animation) {
- // onAnimationEnd() comes during draw(), so there can still be some
- // draw events happening after this call. We don't want to detach
- // the view until after the onAnimationEnd()
- container.post(new Runnable() {
- @Override
- public void run() {
- if (fragment.getAnimatingAway() != null) {
- fragment.setAnimatingAway(null);
- callback.onComplete(fragment, signal);
- }
- }
- });
- }
-
- @Override
- public void onAnimationRepeat(Animation animation) {
- }
- });
- fragment.mView.startAnimation(animation);
- } else {
- Animator animator = anim.animator;
- fragment.setAnimator(anim.animator);
- animator.addListener(new AnimatorListenerAdapter() {
- @Override
- public void onAnimationEnd(Animator anim) {
- container.endViewTransition(viewToAnimate);
- // If an animator ends immediately, we can just pretend there is no animation.
- // When that happens the the fragment's view won't have been removed yet.
- Animator animator = fragment.getAnimator();
- fragment.setAnimator(null);
- if (animator != null && container.indexOfChild(viewToAnimate) < 0) {
- callback.onComplete(fragment, signal);
- }
- }
- });
- animator.setTarget(fragment.mView);
- animator.start();
- }
- }
-
@AnimRes
private static int transitToAnimResourceId(@NonNull Context context, int transit,
boolean enter) {
diff --git a/fragment/fragment/src/main/java/androidx/fragment/app/FragmentManager.java b/fragment/fragment/src/main/java/androidx/fragment/app/FragmentManager.java
index 244bbce..0ff6ea8 100644
--- a/fragment/fragment/src/main/java/androidx/fragment/app/FragmentManager.java
+++ b/fragment/fragment/src/main/java/androidx/fragment/app/FragmentManager.java
@@ -21,8 +21,6 @@
import static androidx.activity.result.contract.ActivityResultContracts.StartIntentSenderForResult.EXTRA_INTENT_SENDER_REQUEST;
import static androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX;
-import android.animation.Animator;
-import android.animation.AnimatorListenerAdapter;
import android.annotation.SuppressLint;
import android.app.Activity;
import android.content.Context;
@@ -61,8 +59,6 @@
import androidx.annotation.Nullable;
import androidx.annotation.RestrictTo;
import androidx.annotation.StringRes;
-import androidx.collection.ArraySet;
-import androidx.core.os.CancellationSignal;
import androidx.fragment.R;
import androidx.fragment.app.strictmode.FragmentStrictMode;
import androidx.lifecycle.Lifecycle;
@@ -98,33 +94,6 @@
public abstract class FragmentManager implements FragmentResultOwner {
private static boolean DEBUG = false;
static final String TAG = "FragmentManager";
- static boolean USE_STATE_MANAGER = true;
-
- /**
- * Control whether FragmentManager uses the new state manager that is responsible for:
- * <ul>
- * <li>Moving Fragments through their lifecycle methods</li>
- * <li>Running animations and transitions</li>
- * <li>Handling postponed transactions</li>
- * </ul>
- *
- * This must only be changed <strong>before</strong> any fragment transactions are done
- * (i.e., in your <code>Application</code> class or prior to <code>super.onCreate()</code>
- * in every activity with the same value for all activities). Changing it after that point
- * is <strong>not</strong> supported and can result in fragments not moving to their
- * expected state.
- * <p>
- * This is <strong>enabled</strong> by default. Disabling it should only be used in
- * cases where you are debugging a potential regression and as part of
- * <a href="https://issuetracker.google.com/issues/new?component=460964">filing
- * an issue</a> to verify and fix the regression.
- *
- * @param enabled Whether the new state manager should be enabled.
- */
- @FragmentStateManagerControl
- public static void enableNewStateManager(boolean enabled) {
- FragmentManager.USE_STATE_MANAGER = enabled;
- }
/**
* Control whether the framework's internal fragment manager debugging
@@ -450,23 +419,6 @@
Collections.synchronizedMap(new HashMap<String, LifecycleAwareResultListener>());
private ArrayList<OnBackStackChangedListener> mBackStackChangeListeners;
- private Map<Fragment, HashSet<CancellationSignal>> mExitAnimationCancellationSignals =
- Collections.synchronizedMap(new HashMap<Fragment, HashSet<CancellationSignal>>());
- private final FragmentTransition.Callback mFragmentTransitionCallback =
- new FragmentTransition.Callback() {
- @Override
- public void onStart(@NonNull Fragment fragment,
- @NonNull CancellationSignal signal) {
- addCancellationSignal(fragment, signal);
- }
-
- @Override
- public void onComplete(@NonNull Fragment f, @NonNull CancellationSignal signal) {
- if (!signal.isCanceled()) {
- removeCancellationSignal(f, signal);
- }
- }
- };
private final FragmentLifecycleCallbacksDispatcher mLifecycleCallbacksDispatcher =
new FragmentLifecycleCallbacksDispatcher(this);
private final CopyOnWriteArrayList<FragmentOnAttachListener> mOnAttachListeners =
@@ -518,9 +470,6 @@
private ArrayList<Boolean> mTmpIsPop;
private ArrayList<Fragment> mTmpAddedFragments;
- // Postponed transactions.
- private ArrayList<StartEnterTransitionListener> mPostponedTransactions;
-
private FragmentManagerViewModel mNonConfig;
private FragmentStrictMode.Policy mStrictModePolicy;
@@ -866,36 +815,6 @@
}
}
- /**
- * Add new {@link CancellationSignal} for exit animation cancel callbacks
- */
- void addCancellationSignal(@NonNull Fragment f, @NonNull CancellationSignal signal) {
- if (mExitAnimationCancellationSignals.get(f) == null) {
- mExitAnimationCancellationSignals.put(f, new HashSet<CancellationSignal>());
- }
- mExitAnimationCancellationSignals.get(f).add(signal);
- }
-
- /**
- * Remove a {@link CancellationSignal} that was previously added with
- * {@link #addCancellationSignal(Fragment, CancellationSignal)}.
- *
- * Destroy the view of the Fragment associated with that listener and move it to the proper
- * state.
- */
- void removeCancellationSignal(@NonNull Fragment f, @NonNull CancellationSignal signal) {
- HashSet<CancellationSignal> signals = mExitAnimationCancellationSignals.get(f);
- if (signals != null && signals.remove(signal) && signals.isEmpty()) {
- mExitAnimationCancellationSignals.remove(f);
- // The Fragment state must be below STARTED before destroying the view to ensure we
- // support hide/show
- if (f.mState < Fragment.STARTED) {
- destroyFragmentView(f);
- moveToState(f);
- }
- }
- }
-
@Override
public final void setFragmentResult(@NonNull String requestKey, @NonNull Bundle result) {
// Check if there is a listener waiting for a result with this key
@@ -1356,11 +1275,7 @@
return;
}
f.mDeferStart = false;
- if (USE_STATE_MANAGER) {
- fragmentStateManager.moveToExpectedState();
- } else {
- moveToState(f);
- }
+ fragmentStateManager.moveToExpectedState();
}
}
@@ -1368,190 +1283,6 @@
return mCurState >= state;
}
- @SuppressWarnings("deprecation")
- void moveToState(@NonNull Fragment f, int newState) {
- FragmentStateManager fragmentStateManager = mFragmentStore.getFragmentStateManager(f.mWho);
- if (fragmentStateManager == null) {
- // Ideally, we only call moveToState() on active Fragments. However,
- // in restoreSaveState() we can call moveToState() on retained Fragments
- // just to clean them up without them ever being added to mActive.
- // For these cases, a brand new FragmentStateManager is enough.
- fragmentStateManager = new FragmentStateManager(mLifecycleCallbacksDispatcher,
- mFragmentStore, f);
- // Only allow this FragmentStateManager to go up to CREATED at the most
- fragmentStateManager.setFragmentManagerState(Fragment.CREATED);
- }
- // When inflating an Activity view with a resource instead of using setContentView(), and
- // that resource adds a fragment using the <fragment> tag (i.e. from layout and in layout),
- // the fragment will move to the VIEW_CREATED state before the fragment manager
- // moves to CREATED. So when moving the fragment manager moves to CREATED and the
- // inflated fragment is already in VIEW_CREATED we need to move new state up from CREATED
- // to VIEW_CREATED. This avoids accidentally moving the fragment back down to CREATED
- // which would immediately destroy the Fragment's view. We rely on computeExpectedState()
- // to pull the state back down if needed.
- if (f.mFromLayout && f.mInLayout && f.mState == Fragment.VIEW_CREATED) {
- newState = Math.max(newState, Fragment.VIEW_CREATED);
- }
- newState = Math.min(newState, fragmentStateManager.computeExpectedState());
- if (f.mState <= newState) {
- // If we are moving to the same state, we do not need to give up on the animation.
- if (f.mState < newState && !mExitAnimationCancellationSignals.isEmpty()) {
- // The fragment is currently being animated... but! Now we
- // want to move our state back up. Give up on waiting for the
- // animation and proceed from where we are.
- cancelExitAnimation(f);
- }
- switch (f.mState) {
- case Fragment.INITIALIZING:
- if (newState > Fragment.INITIALIZING) {
- fragmentStateManager.attach();
- }
- // fall through
- case Fragment.ATTACHED:
- if (newState > Fragment.ATTACHED) {
- fragmentStateManager.create();
- }
- // fall through
- case Fragment.CREATED:
- // We want to unconditionally run this anytime we do a moveToState that
- // moves the Fragment above INITIALIZING, including cases such as when
- // we move from CREATED => CREATED as part of the case fall through above.
- if (newState > Fragment.INITIALIZING) {
- fragmentStateManager.ensureInflatedView();
- }
-
- if (newState > Fragment.CREATED) {
- fragmentStateManager.createView();
- }
- // fall through
- case Fragment.VIEW_CREATED:
- if (newState > Fragment.VIEW_CREATED) {
- fragmentStateManager.activityCreated();
- }
- // fall through
- case Fragment.ACTIVITY_CREATED:
- if (newState > Fragment.ACTIVITY_CREATED) {
- fragmentStateManager.start();
- }
- // fall through
- case Fragment.STARTED:
- if (newState > Fragment.STARTED) {
- fragmentStateManager.resume();
- }
- }
- } else if (f.mState > newState) {
- switch (f.mState) {
- case Fragment.RESUMED:
- if (newState < Fragment.RESUMED) {
- fragmentStateManager.pause();
- }
- // fall through
- case Fragment.STARTED:
- if (newState < Fragment.STARTED) {
- fragmentStateManager.stop();
- }
- // fall through
- case Fragment.ACTIVITY_CREATED:
- if (newState < Fragment.ACTIVITY_CREATED) {
- if (isLoggingEnabled(Log.DEBUG)) {
- Log.d(TAG, "movefrom ACTIVITY_CREATED: " + f);
- }
- if (f.mView != null) {
- // Need to save the current view state if not
- // done already.
- if (mHost.onShouldSaveFragmentState(f) && f.mSavedViewState == null) {
- fragmentStateManager.saveViewState();
- }
- }
- }
- // fall through
- case Fragment.VIEW_CREATED:
- if (newState < Fragment.VIEW_CREATED) {
- FragmentAnim.AnimationOrAnimator anim = null;
- if (f.mView != null && f.mContainer != null) {
- // Stop any current animations:
- f.mContainer.endViewTransition(f.mView);
- f.mView.clearAnimation();
- // If parent is being removed, no need to handle child animations.
- if (!f.isRemovingParent()) {
- if (mCurState > Fragment.INITIALIZING && !mDestroyed
- && f.mView.getVisibility() == View.VISIBLE
- && f.mPostponedAlpha >= 0) {
- anim = FragmentAnim.loadAnimation(mHost.getContext(),
- f, false, f.getPopDirection());
- }
- f.mPostponedAlpha = 0;
- // Robolectric tests do not post the animation like a real device
- // so we should keep up with the container and view in case the
- // fragment view is destroyed before we can remove it.
- ViewGroup container = f.mContainer;
- View view = f.mView;
- if (anim != null) {
- FragmentAnim.animateRemoveFragment(f, anim,
- mFragmentTransitionCallback);
- }
- container.removeView(view);
- if (FragmentManager.isLoggingEnabled(Log.VERBOSE)) {
- Log.v(FragmentManager.TAG, "Removing view " + view + " for "
- + "fragment " + f + " from container " + container);
- }
- // If the local container is different from the fragment
- // container, that means onAnimationEnd was called, onDestroyView
- // was dispatched and the fragment was already moved to state, so
- // we should early return here instead of attempting to move to
- // state again.
- if (container != f.mContainer) {
- return;
- }
- }
- }
- // If a fragment has an exit animation (or transition), do not destroy
- // its view immediately and set the state after animating
- if (mExitAnimationCancellationSignals.get(f) == null) {
- fragmentStateManager.destroyFragmentView();
- }
- }
- // fall through
- case Fragment.CREATED:
- if (newState < Fragment.CREATED) {
- if (mExitAnimationCancellationSignals.get(f) != null) {
- // We are waiting for the fragment's view to finish animating away.
- newState = Fragment.CREATED;
- } else {
- fragmentStateManager.destroy();
- }
- }
- // fall through
- case Fragment.ATTACHED:
- if (newState < Fragment.ATTACHED) {
- fragmentStateManager.detach();
- }
- }
- }
-
- if (f.mState != newState) {
- if (isLoggingEnabled(Log.DEBUG)) {
- Log.d(TAG, "moveToState: Fragment state for " + f + " not updated inline; "
- + "expected state " + newState + " found " + f.mState);
- }
- f.mState = newState;
- }
- }
-
- // If there is a listener associated with the given fragment, remove that listener and
- // destroy the fragment's view.
- private void cancelExitAnimation(@NonNull Fragment f) {
- HashSet<CancellationSignal> signals = mExitAnimationCancellationSignals.get(f);
- if (signals != null) {
- for (CancellationSignal signal: signals) {
- signal.cancel();
- }
- signals.clear();
- destroyFragmentView(f);
- mExitAnimationCancellationSignals.remove(f);
- }
- }
-
/**
* Allows for changing the draw order on a container, if the container is a
* FragmentContainerView.
@@ -1565,123 +1296,6 @@
}
}
- private void destroyFragmentView(@NonNull Fragment fragment) {
- fragment.performDestroyView();
- mLifecycleCallbacksDispatcher.dispatchOnFragmentViewDestroyed(fragment, false);
- fragment.mContainer = null;
- fragment.mView = null;
- // Set here to ensure that Observers are called after
- // the Fragment's view is set to null
- fragment.mViewLifecycleOwner = null;
- fragment.mViewLifecycleOwnerLiveData.setValue(null);
- fragment.mInLayout = false;
- }
-
- void moveToState(@NonNull Fragment f) {
- moveToState(f, mCurState);
- }
-
- /**
- * Fragments that have been shown or hidden don't have their visibility changed or
- * animations run during the {@link #showFragment(Fragment)} or {@link #hideFragment(Fragment)}
- * calls. After fragments are brought to their final state in
- * {@link #moveFragmentToExpectedState(Fragment)} the fragments that have been shown or
- * hidden must have their visibility changed and their animations started here.
- *
- * @param fragment The fragment with mHiddenChanged = true that should change its View's
- * visibility and start the show or hide animation.
- */
- private void completeShowHideFragment(@NonNull final Fragment fragment) {
- if (fragment.mView != null) {
- FragmentAnim.AnimationOrAnimator anim = FragmentAnim.loadAnimation(
- mHost.getContext(), fragment, !fragment.mHidden, fragment.getPopDirection());
- if (anim != null && anim.animator != null) {
- anim.animator.setTarget(fragment.mView);
- if (fragment.mHidden) {
- if (fragment.isHideReplaced()) {
- fragment.setHideReplaced(false);
- } else {
- final ViewGroup container = fragment.mContainer;
- final View animatingView = fragment.mView;
- container.startViewTransition(animatingView);
- // Delay the actual hide operation until the animation finishes,
- // otherwise the fragment will just immediately disappear
- anim.animator.addListener(new AnimatorListenerAdapter() {
- @Override
- public void onAnimationEnd(Animator animation) {
- container.endViewTransition(animatingView);
- animation.removeListener(this);
- if (fragment.mView != null && fragment.mHidden) {
- fragment.mView.setVisibility(View.GONE);
- }
- }
- });
- }
- } else {
- fragment.mView.setVisibility(View.VISIBLE);
- }
- anim.animator.start();
- } else {
- if (anim != null) {
- fragment.mView.startAnimation(anim.animation);
- anim.animation.start();
- }
- final int visibility = fragment.mHidden && !fragment.isHideReplaced()
- ? View.GONE
- : View.VISIBLE;
- fragment.mView.setVisibility(visibility);
- if (fragment.isHideReplaced()) {
- fragment.setHideReplaced(false);
- }
- }
- }
- invalidateMenuForFragment(fragment);
- fragment.mHiddenChanged = false;
- fragment.onHiddenChanged(fragment.mHidden);
- }
-
- /**
- * Moves a fragment to its expected final state or the fragment manager's state, depending
- * on whether the fragment manager's state is raised properly.
- *
- * @param f The fragment to change.
- */
- void moveFragmentToExpectedState(@NonNull Fragment f) {
- if (!mFragmentStore.containsActiveFragment(f.mWho)) {
- if (isLoggingEnabled(Log.DEBUG)) {
- Log.d(TAG, "Ignoring moving " + f + " to state " + mCurState
- + "since it is not added to " + this);
- }
- return;
- }
- moveToState(f);
-
- if (f.mView != null) {
- if (f.mIsNewlyAdded && f.mContainer != null) {
- // Make it visible and run the animations
- if (f.mPostponedAlpha > 0f) {
- f.mView.setAlpha(f.mPostponedAlpha);
- }
- f.mPostponedAlpha = 0f;
- f.mIsNewlyAdded = false;
- // run animations:
- FragmentAnim.AnimationOrAnimator anim = FragmentAnim.loadAnimation(
- mHost.getContext(), f, true, f.getPopDirection());
- if (anim != null) {
- if (anim.animation != null) {
- f.mView.startAnimation(anim.animation);
- } else {
- anim.animator.setTarget(f.mView);
- anim.animator.start();
- }
- }
- }
- }
- if (f.mHiddenChanged) {
- completeShowHideFragment(f);
- }
- }
-
/**
* Changes the state of the fragment manager to {@code newState}. If the fragment manager
* changes state or {@code always} is {@code true}, any fragments within it have their
@@ -1701,30 +1315,7 @@
}
mCurState = newState;
-
- if (USE_STATE_MANAGER) {
- mFragmentStore.moveToExpectedState();
- } else {
- // Must add them in the proper order. mActive fragments may be out of order
- for (Fragment f : mFragmentStore.getFragments()) {
- moveFragmentToExpectedState(f);
- }
-
- // Now iterate through all active fragments. These will include those that are removed
- // and detached.
- for (FragmentStateManager fragmentStateManager :
- mFragmentStore.getActiveFragmentStateManagers()) {
- Fragment f = fragmentStateManager.getFragment();
- if (!f.mIsNewlyAdded) {
- moveFragmentToExpectedState(f);
- }
- boolean beingRemoved = f.mRemoving && !f.isInBackStack();
- if (beingRemoved) {
- mFragmentStore.makeInactive(fragmentStateManager);
- }
- }
- }
-
+ mFragmentStore.moveToExpectedState();
startPendingDeferredFragments();
if (mNeedMenuInvalidate && mHost != null && mCurState == Fragment.RESUMED) {
@@ -1800,8 +1391,7 @@
}
/**
- * Marks a fragment as hidden to be later animated in with
- * {@link #completeShowHideFragment(Fragment)}.
+ * Marks a fragment as hidden to be later animated.
*
* @param fragment The fragment to be shown.
*/
@@ -1817,8 +1407,7 @@
}
/**
- * Marks a fragment as shown to be later animated in with
- * {@link #completeShowHideFragment(Fragment)}.
+ * Marks a fragment as shown to be later animated.
*
* @param fragment The fragment to be shown.
*/
@@ -1967,10 +1556,8 @@
@SuppressWarnings("WeakerAccess") /* synthetic access */
void scheduleCommit() {
synchronized (mPendingActions) {
- boolean postponeReady =
- mPostponedTransactions != null && !mPostponedTransactions.isEmpty();
boolean pendingReady = mPendingActions.size() == 1;
- if (postponeReady || pendingReady) {
+ if (pendingReady) {
mHost.getHandler().removeCallbacks(mExecCommit);
mHost.getHandler().post(mExecCommit);
updateOnBackPressedCallbackEnabled();
@@ -2013,12 +1600,6 @@
mTmpRecords = new ArrayList<>();
mTmpIsPop = new ArrayList<>();
}
- mExecutingActions = true;
- try {
- executePostponedTransaction(null, null);
- } finally {
- mExecutingActions = false;
- }
}
void execSingleAction(@NonNull OpGenerator action, boolean allowStateLoss) {
@@ -2076,44 +1657,6 @@
}
/**
- * Complete the execution of transactions that have previously been postponed, but are
- * now ready.
- */
- private void executePostponedTransaction(@Nullable ArrayList<BackStackRecord> records,
- @Nullable ArrayList<Boolean> isRecordPop) {
- int numPostponed = mPostponedTransactions == null ? 0 : mPostponedTransactions.size();
- for (int i = 0; i < numPostponed; i++) {
- StartEnterTransitionListener listener = mPostponedTransactions.get(i);
- if (records != null && !listener.mIsBack) {
- int index = records.indexOf(listener.mRecord);
- if (index != -1 && isRecordPop != null && isRecordPop.get(index)) {
- mPostponedTransactions.remove(i);
- i--;
- numPostponed--;
- listener.cancelTransaction();
- continue;
- }
- }
- if (listener.isReady() || (records != null
- && listener.mRecord.interactsWith(records, 0, records.size()))) {
- mPostponedTransactions.remove(i);
- i--;
- numPostponed--;
- int index;
- if (records != null && !listener.mIsBack
- && (index = records.indexOf(listener.mRecord)) != -1
- && isRecordPop != null
- && isRecordPop.get(index)) {
- // This is popping a postponed transaction
- listener.cancelTransaction();
- } else {
- listener.completeTransaction();
- }
- }
- }
- }
-
- /**
* Remove redundant BackStackRecord operations and executes them. This method merges operations
* of proximate records that allow reordering. See
* {@link FragmentTransaction#setReorderingAllowed(boolean)}.
@@ -2137,9 +1680,6 @@
throw new IllegalStateException("Internal error with the back stack records");
}
- // Force start of any postponed transactions that interact with scheduled transactions:
- executePostponedTransaction(records, isRecordPop);
-
final int numRecords = records.size();
int startIndex = 0;
for (int recordNum = 0; recordNum < numRecords; recordNum++) {
@@ -2201,24 +1741,18 @@
mTmpAddedFragments.clear();
if (!allowReordering && mCurState >= Fragment.CREATED) {
- if (USE_STATE_MANAGER) {
- // When reordering isn't allowed, we may be operating on Fragments that haven't
- // been made active
- for (int index = startIndex; index < endIndex; index++) {
- BackStackRecord record = records.get(index);
- for (FragmentTransaction.Op op : record.mOps) {
- Fragment fragment = op.mFragment;
- if (fragment != null && fragment.mFragmentManager != null) {
- FragmentStateManager fragmentStateManager =
- createOrGetFragmentStateManager(fragment);
- mFragmentStore.makeActive(fragmentStateManager);
- }
+ // When reordering isn't allowed, we may be operating on Fragments that haven't
+ // been made active
+ for (int index = startIndex; index < endIndex; index++) {
+ BackStackRecord record = records.get(index);
+ for (FragmentTransaction.Op op : record.mOps) {
+ Fragment fragment = op.mFragment;
+ if (fragment != null && fragment.mFragmentManager != null) {
+ FragmentStateManager fragmentStateManager =
+ createOrGetFragmentStateManager(fragment);
+ mFragmentStore.makeActive(fragmentStateManager);
}
}
- } else {
- FragmentTransition.startTransitions(mHost.getContext(), mContainer,
- records, isRecordPop, startIndex, endIndex,
- false, mFragmentTransitionCallback);
}
}
executeOps(records, isRecordPop, startIndex, endIndex);
@@ -2228,70 +1762,49 @@
record.runOnExecuteRunnables();
}
- if (USE_STATE_MANAGER) {
- // The last operation determines the overall direction, this ensures that operations
- // such as push, push, pop, push are correctly considered a push
- boolean isPop = isRecordPop.get(endIndex - 1);
- // Ensure that Fragments directly affected by operations
- // are moved to their expected state in operation order
- for (int index = startIndex; index < endIndex; index++) {
- BackStackRecord record = records.get(index);
- if (isPop) {
- // Pop operations get applied in reverse order
- for (int opIndex = record.mOps.size() - 1; opIndex >= 0; opIndex--) {
- FragmentTransaction.Op op = record.mOps.get(opIndex);
- Fragment fragment = op.mFragment;
- if (fragment != null) {
- FragmentStateManager fragmentStateManager =
- createOrGetFragmentStateManager(fragment);
- fragmentStateManager.moveToExpectedState();
- }
- }
- } else {
- for (FragmentTransaction.Op op : record.mOps) {
- Fragment fragment = op.mFragment;
- if (fragment != null) {
- FragmentStateManager fragmentStateManager =
- createOrGetFragmentStateManager(fragment);
- fragmentStateManager.moveToExpectedState();
- }
+ // The last operation determines the overall direction, this ensures that operations
+ // such as push, push, pop, push are correctly considered a push
+ boolean isPop = isRecordPop.get(endIndex - 1);
+ // Ensure that Fragments directly affected by operations
+ // are moved to their expected state in operation order
+ for (int index = startIndex; index < endIndex; index++) {
+ BackStackRecord record = records.get(index);
+ if (isPop) {
+ // Pop operations get applied in reverse order
+ for (int opIndex = record.mOps.size() - 1; opIndex >= 0; opIndex--) {
+ FragmentTransaction.Op op = record.mOps.get(opIndex);
+ Fragment fragment = op.mFragment;
+ if (fragment != null) {
+ FragmentStateManager fragmentStateManager =
+ createOrGetFragmentStateManager(fragment);
+ fragmentStateManager.moveToExpectedState();
}
}
-
- }
- // And only then do we move all other fragments to the current state
- moveToState(mCurState, true);
- Set<SpecialEffectsController> changedControllers = collectChangedControllers(
- records, startIndex, endIndex);
- for (SpecialEffectsController controller : changedControllers) {
- controller.updateOperationDirection(isPop);
- controller.markPostponedState();
- controller.executePendingOperations();
- }
- } else {
- int postponeIndex = endIndex;
- if (allowReordering) {
- ArraySet<Fragment> addedFragments = new ArraySet<>();
- addAddedFragments(addedFragments);
- postponeIndex = postponePostponableTransactions(records, isRecordPop,
- startIndex, endIndex, addedFragments);
- makeRemovedFragmentsInvisible(addedFragments);
- }
-
- if (postponeIndex != startIndex && allowReordering) {
- // need to run something now
- if (mCurState >= Fragment.CREATED) {
- FragmentTransition.startTransitions(mHost.getContext(), mContainer,
- records, isRecordPop, startIndex,
- postponeIndex, true, mFragmentTransitionCallback);
+ } else {
+ for (FragmentTransaction.Op op : record.mOps) {
+ Fragment fragment = op.mFragment;
+ if (fragment != null) {
+ FragmentStateManager fragmentStateManager =
+ createOrGetFragmentStateManager(fragment);
+ fragmentStateManager.moveToExpectedState();
+ }
}
- moveToState(mCurState, true);
}
+
+ }
+ // And only then do we move all other fragments to the current state
+ moveToState(mCurState, true);
+ Set<SpecialEffectsController> changedControllers = collectChangedControllers(
+ records, startIndex, endIndex);
+ for (SpecialEffectsController controller : changedControllers) {
+ controller.updateOperationDirection(isPop);
+ controller.markPostponedState();
+ controller.executePendingOperations();
}
for (int recordNum = startIndex; recordNum < endIndex; recordNum++) {
final BackStackRecord record = records.get(recordNum);
- final boolean isPop = isRecordPop.get(recordNum);
+ isPop = isRecordPop.get(recordNum);
if (isPop && record.mIndex >= 0) {
record.mIndex = -1;
}
@@ -2322,132 +1835,6 @@
}
/**
- * Any fragments that were removed because they have been postponed should have their views
- * made invisible by setting their alpha to 0.
- *
- * @param fragments The fragments that were added during operation execution. Only the ones
- * that are no longer added will have their alpha changed.
- */
- private void makeRemovedFragmentsInvisible(@NonNull ArraySet<Fragment> fragments) {
- final int numAdded = fragments.size();
- for (int i = 0; i < numAdded; i++) {
- final Fragment fragment = fragments.valueAt(i);
- if (!fragment.mAdded) {
- final View view = fragment.requireView();
- fragment.mPostponedAlpha = view.getAlpha();
- view.setAlpha(0f);
- }
- }
- }
-
- /**
- * Examine all transactions and determine which ones are marked as postponed. Those will
- * have their operations rolled back and moved to the end of the record list (up to endIndex).
- * It will also add the postponed transaction to the queue.
- *
- * @param records A list of BackStackRecords that should be checked.
- * @param isRecordPop The direction that these records are being run.
- * @param startIndex The index of the first record in <code>records</code> to be checked
- * @param endIndex One more than the final record index in <code>records</code> to be checked.
- * @return The index of the first postponed transaction or endIndex if no transaction was
- * postponed.
- */
- private int postponePostponableTransactions(@NonNull ArrayList<BackStackRecord> records,
- @NonNull ArrayList<Boolean> isRecordPop, int startIndex, int endIndex,
- @NonNull ArraySet<Fragment> added) {
- int postponeIndex = endIndex;
- for (int i = endIndex - 1; i >= startIndex; i--) {
- final BackStackRecord record = records.get(i);
- final boolean isPop = isRecordPop.get(i);
- boolean isPostponed = record.isPostponed()
- && !record.interactsWith(records, i + 1, endIndex);
- if (isPostponed) {
- if (mPostponedTransactions == null) {
- mPostponedTransactions = new ArrayList<>();
- }
- StartEnterTransitionListener listener =
- new StartEnterTransitionListener(record, isPop);
- mPostponedTransactions.add(listener);
- record.setOnStartPostponedListener(listener);
-
- // roll back the transaction
- if (isPop) {
- record.executeOps();
- } else {
- record.executePopOps(false);
- }
-
- // move to the end
- postponeIndex--;
- if (i != postponeIndex) {
- records.remove(i);
- records.add(postponeIndex, record);
- }
-
- // different views may be visible now
- addAddedFragments(added);
- }
- }
- return postponeIndex;
- }
-
- /**
- * When a postponed transaction is ready to be started, this completes the transaction,
- * removing, hiding, or showing views as well as starting the animations and transitions.
- * <p>
- * {@code runtransitions} is set to false when the transaction postponement was interrupted
- * abnormally -- normally by a new transaction being started that affects the postponed
- * transaction.
- *
- * @param record The transaction to run
- * @param isPop true if record is popping or false if it is adding
- * @param runTransitions true if the fragment transition should be run or false otherwise.
- * @param moveToState true if the state should be changed after executing the operations.
- * This is false when the transaction is canceled when a postponed
- * transaction is popped.
- */
- @SuppressWarnings("WeakerAccess") /* synthetic access */
- void completeExecute(@NonNull BackStackRecord record, boolean isPop, boolean runTransitions,
- boolean moveToState) {
- if (isPop) {
- record.executePopOps(moveToState);
- } else {
- record.executeOps();
- }
- ArrayList<BackStackRecord> records = new ArrayList<>(1);
- ArrayList<Boolean> isRecordPop = new ArrayList<>(1);
- records.add(record);
- isRecordPop.add(isPop);
- if (runTransitions && mCurState >= Fragment.CREATED) {
- FragmentTransition.startTransitions(mHost.getContext(), mContainer,
- records, isRecordPop, 0, 1, true,
- mFragmentTransitionCallback);
- }
- if (moveToState) {
- moveToState(mCurState, true);
- }
-
- for (Fragment fragment : mFragmentStore.getActiveFragments()) {
- // Allow added fragments to be removed during the pop since we aren't going
- // to move them to the final state with moveToState(mCurState).
- if (fragment != null) {
- if (fragment.mView != null && fragment.mIsNewlyAdded
- && record.interactsWith(fragment.mContainerId)) {
- if (fragment.mPostponedAlpha > 0) {
- fragment.mView.setAlpha(fragment.mPostponedAlpha);
- }
- if (moveToState) {
- fragment.mPostponedAlpha = 0;
- } else {
- fragment.mPostponedAlpha = -1;
- fragment.mIsNewlyAdded = false;
- }
- }
- }
- }
- }
-
- /**
* Run the operations in the BackStackRecords, either to push or pop.
*
* @param records The list of records whose operations should be run.
@@ -2462,10 +1849,7 @@
final boolean isPop = isRecordPop.get(i);
if (isPop) {
record.bumpBackStackNesting(-1);
- // Only execute the add operations at the end of
- // all transactions.
- boolean moveToState = i == (endIndex - 1);
- record.executePopOps(moveToState);
+ record.executePopOps();
} else {
record.bumpBackStackNesting(1);
record.executeOps();
@@ -2516,42 +1900,12 @@
}
/**
- * Ensure that fragments that are added are moved to at least the CREATED state.
- * Any newly-added Views are inserted into {@code added} so that the Transaction can be
- * postponed with {@link Fragment#postponeEnterTransition()}. They will later be made
- * invisible (by setting their alpha to 0) if they have been removed when postponed.
- */
- private void addAddedFragments(@NonNull ArraySet<Fragment> added) {
- if (mCurState < Fragment.CREATED) {
- return;
- }
- // We want to leave the fragment in the started state
- final int state = Math.min(mCurState, Fragment.STARTED);
- for (Fragment fragment : mFragmentStore.getFragments()) {
- if (fragment.mState < state) {
- moveToState(fragment, state);
- if (fragment.mView != null && !fragment.mHidden && fragment.mIsNewlyAdded) {
- added.add(fragment);
- }
- }
- }
- }
-
- /**
* Starts all postponed transactions regardless of whether they are ready or not.
*/
private void forcePostponedTransactions() {
- if (USE_STATE_MANAGER) {
- Set<SpecialEffectsController> controllers = collectAllSpecialEffectsController();
- for (SpecialEffectsController controller : controllers) {
- controller.forcePostponedExecutePendingOperations();
- }
- } else {
- if (mPostponedTransactions != null) {
- while (!mPostponedTransactions.isEmpty()) {
- mPostponedTransactions.remove(0).completeTransaction();
- }
- }
+ Set<SpecialEffectsController> controllers = collectAllSpecialEffectsController();
+ for (SpecialEffectsController controller : controllers) {
+ controller.forcePostponedExecutePendingOperations();
}
}
@@ -2560,18 +1914,9 @@
* This is used prior to saving the state so that the correct state is saved.
*/
private void endAnimatingAwayFragments() {
- if (USE_STATE_MANAGER) {
- Set<SpecialEffectsController> controllers = collectAllSpecialEffectsController();
- for (SpecialEffectsController controller : controllers) {
- controller.forceCompleteAllOperations();
- }
- } else {
- if (!mExitAnimationCancellationSignals.isEmpty()) {
- for (Fragment fragment: mExitAnimationCancellationSignals.keySet()) {
- cancelExitAnimation(fragment);
- moveToState(fragment);
- }
- }
+ Set<SpecialEffectsController> controllers = collectAllSpecialEffectsController();
+ for (SpecialEffectsController controller : controllers) {
+ controller.forceCompleteAllOperations();
}
}
@@ -3368,11 +2713,9 @@
mExecutingActions = true;
mFragmentStore.dispatchStateChange(nextState);
moveToState(nextState, false);
- if (USE_STATE_MANAGER) {
- Set<SpecialEffectsController> controllers = collectAllSpecialEffectsController();
- for (SpecialEffectsController controller : controllers) {
- controller.forceCompleteAllOperations();
- }
+ Set<SpecialEffectsController> controllers = collectAllSpecialEffectsController();
+ for (SpecialEffectsController controller : controllers) {
+ controller.forceCompleteAllOperations();
}
} finally {
mExecutingActions = false;
@@ -3835,80 +3178,6 @@
}
}
- /**
- * A listener for a postponed transaction. This waits until
- * {@link Fragment#startPostponedEnterTransition()} is called or a transaction is started
- * that interacts with this one, based on interactions with the fragment container.
- */
- static class StartEnterTransitionListener
- implements Fragment.OnStartEnterTransitionListener {
- final boolean mIsBack;
- final BackStackRecord mRecord;
- private int mNumPostponed;
-
- StartEnterTransitionListener(@NonNull BackStackRecord record, boolean isBack) {
- mIsBack = isBack;
- mRecord = record;
- }
-
- /**
- * Called from {@link Fragment#startPostponedEnterTransition()}, this decreases the
- * number of Fragments that are postponed. This may cause the transaction to schedule
- * to finish running and run transitions and animations.
- */
- @Override
- public void onStartEnterTransition() {
- mNumPostponed--;
- if (mNumPostponed != 0) {
- return;
- }
- mRecord.mManager.scheduleCommit();
- }
-
- /**
- * Called from {@link Fragment#
- * setOnStartEnterTransitionListener(Fragment.OnStartEnterTransitionListener)}, this
- * increases the number of fragments that are postponed as part of this transaction.
- */
- @Override
- public void startListening() {
- mNumPostponed++;
- }
-
- /**
- * @return true if there are no more postponed fragments as part of the transaction.
- */
- public boolean isReady() {
- return mNumPostponed == 0;
- }
-
- /**
- * Completes the transaction and start the animations and transitions. This may skip
- * the transitions if this is called before all fragments have called
- * {@link Fragment#startPostponedEnterTransition()}.
- */
- void completeTransaction() {
- final boolean canceled;
- canceled = mNumPostponed > 0;
- FragmentManager manager = mRecord.mManager;
- for (Fragment fragment : manager.getFragments()) {
- fragment.setOnStartEnterTransitionListener(null);
- if (canceled && fragment.isPostponed()) {
- fragment.startPostponedEnterTransition();
- }
- }
- mRecord.mManager.completeExecute(mRecord, mIsBack, !canceled, true);
- }
-
- /**
- * Cancels this transaction instead of completing it. That means that the state isn't
- * changed, so the pop results in no change to the state.
- */
- void cancelTransaction() {
- mRecord.mManager.completeExecute(mRecord, mIsBack, false, false);
- }
- }
-
@SuppressLint("BanParcelableUsage")
static class LaunchedFragmentInfo implements Parcelable {
String mWho;
diff --git a/fragment/fragment/src/main/java/androidx/fragment/app/FragmentStateManager.java b/fragment/fragment/src/main/java/androidx/fragment/app/FragmentStateManager.java
index 7d24a8c..5ebc691 100644
--- a/fragment/fragment/src/main/java/androidx/fragment/app/FragmentStateManager.java
+++ b/fragment/fragment/src/main/java/androidx/fragment/app/FragmentStateManager.java
@@ -199,7 +199,7 @@
maxState = Math.min(maxState, Fragment.CREATED);
}
SpecialEffectsController.Operation.LifecycleImpact awaitingEffect = null;
- if (FragmentManager.USE_STATE_MANAGER && mFragment.mContainer != null) {
+ if (mFragment.mContainer != null) {
SpecialEffectsController controller = SpecialEffectsController.getOrCreateController(
mFragment.mContainer, mFragment.getParentFragmentManager());
awaitingEffect = controller.getAwaitingCompletionLifecycleImpact(this);
@@ -338,7 +338,7 @@
}
}
}
- if (FragmentManager.USE_STATE_MANAGER && mFragment.mHiddenChanged) {
+ if (mFragment.mHiddenChanged) {
if (mFragment.mView != null && mFragment.mContainer != null) {
// Get the controller and enqueue the show/hide
SpecialEffectsController controller = SpecialEffectsController
@@ -436,10 +436,7 @@
targetFragmentStateManager = null;
}
if (targetFragmentStateManager != null) {
- if (FragmentManager.USE_STATE_MANAGER
- || targetFragmentStateManager.getFragment().mState < Fragment.CREATED) {
- targetFragmentStateManager.moveToExpectedState();
- }
+ targetFragmentStateManager.moveToExpectedState();
}
mFragment.mHost = mFragment.mFragmentManager.getHost();
mFragment.mParentFragment = mFragment.mFragmentManager.getParent();
@@ -534,26 +531,19 @@
mFragment, mFragment.mView, mFragment.mSavedFragmentState, false);
int postOnViewCreatedVisibility = mFragment.mView.getVisibility();
float postOnViewCreatedAlpha = mFragment.mView.getAlpha();
- if (FragmentManager.USE_STATE_MANAGER) {
- mFragment.setPostOnViewCreatedAlpha(postOnViewCreatedAlpha);
- if (mFragment.mContainer != null && postOnViewCreatedVisibility == View.VISIBLE) {
- // Save the focused view if one was set via requestFocus()
- View focusedView = mFragment.mView.findFocus();
- if (focusedView != null) {
- mFragment.setFocusedView(focusedView);
- if (FragmentManager.isLoggingEnabled(Log.VERBOSE)) {
- Log.v(TAG, "requestFocus: Saved focused view " + focusedView
- + " for Fragment " + mFragment);
- }
+ mFragment.setPostOnViewCreatedAlpha(postOnViewCreatedAlpha);
+ if (mFragment.mContainer != null && postOnViewCreatedVisibility == View.VISIBLE) {
+ // Save the focused view if one was set via requestFocus()
+ View focusedView = mFragment.mView.findFocus();
+ if (focusedView != null) {
+ mFragment.setFocusedView(focusedView);
+ if (FragmentManager.isLoggingEnabled(Log.VERBOSE)) {
+ Log.v(TAG, "requestFocus: Saved focused view " + focusedView
+ + " for Fragment " + mFragment);
}
- // Set the view alpha to 0
- mFragment.mView.setAlpha(0f);
}
- } else {
- // Only animate the view if it is visible. This is done after
- // dispatchOnFragmentViewCreated in case visibility is changed
- mFragment.mIsNewlyAdded = (postOnViewCreatedVisibility == View.VISIBLE)
- && mFragment.mContainer != null;
+ // Set the view alpha to 0
+ mFragment.mView.setAlpha(0f);
}
}
mFragment.mState = Fragment.VIEW_CREATED;
diff --git a/fragment/fragment/src/main/java/androidx/fragment/app/FragmentStateManagerControl.java b/fragment/fragment/src/main/java/androidx/fragment/app/FragmentStateManagerControl.java
deleted file mode 100644
index de8bdbe..0000000
--- a/fragment/fragment/src/main/java/androidx/fragment/app/FragmentStateManagerControl.java
+++ /dev/null
@@ -1,33 +0,0 @@
-/*
- * Copyright 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package androidx.fragment.app;
-
-import androidx.annotation.experimental.Experimental;
-
-import java.lang.annotation.ElementType;
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-import java.lang.annotation.Target;
-
-/**
- * @see FragmentManager#enableNewStateManager(boolean)
- */
-@Retention(RetentionPolicy.CLASS)
-@Target({ElementType.METHOD})
-@Experimental(level = Experimental.Level.WARNING)
-public @interface FragmentStateManagerControl {
-}
diff --git a/fragment/fragment/src/main/java/androidx/fragment/app/FragmentTransition.java b/fragment/fragment/src/main/java/androidx/fragment/app/FragmentTransition.java
index c4729ca..6ee0e076 100644
--- a/fragment/fragment/src/main/java/androidx/fragment/app/FragmentTransition.java
+++ b/fragment/fragment/src/main/java/androidx/fragment/app/FragmentTransition.java
@@ -15,65 +15,21 @@
*/
package androidx.fragment.app;
-import android.content.Context;
-import android.graphics.Rect;
import android.os.Build;
-import android.util.SparseArray;
import android.view.View;
-import android.view.ViewGroup;
import androidx.annotation.NonNull;
import androidx.collection.ArrayMap;
import androidx.core.app.SharedElementCallback;
-import androidx.core.os.CancellationSignal;
-import androidx.core.view.OneShotPreDrawListener;
-import androidx.core.view.ViewCompat;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
-import java.util.Map;
/**
- * Contains the Fragment Transition functionality for both ordered and reordered
- * Fragment Transactions. With reordered fragment transactions, all Views have been
- * added to the View hierarchy prior to calling startTransitions. With ordered
- * fragment transactions, Views will be removed and added after calling startTransitions.
+ * Contains the Fragment Transition functionality.
*/
class FragmentTransition {
- /**
- * The inverse of all BackStackRecord operation commands. This assumes that
- * REPLACE operations have already been replaced by add/remove operations.
- */
- private static final int[] INVERSE_OPS = {
- BackStackRecord.OP_NULL, // inverse of OP_NULL (error)
- BackStackRecord.OP_REMOVE, // inverse of OP_ADD
- BackStackRecord.OP_NULL, // inverse of OP_REPLACE (error)
- BackStackRecord.OP_ADD, // inverse of OP_REMOVE
- BackStackRecord.OP_SHOW, // inverse of OP_HIDE
- BackStackRecord.OP_HIDE, // inverse of OP_SHOW
- BackStackRecord.OP_ATTACH, // inverse of OP_DETACH
- BackStackRecord.OP_DETACH, // inverse of OP_ATTACH
- BackStackRecord.OP_UNSET_PRIMARY_NAV, // inverse of OP_SET_PRIMARY_NAV
- BackStackRecord.OP_SET_PRIMARY_NAV, // inverse of OP_UNSET_PRIMARY_NAV
- BackStackRecord.OP_SET_MAX_LIFECYCLE
- };
-
- /**
- * Interface to watch Fragment Transitions
- */
- interface Callback {
- /**
- * Called whenever an transition started
- */
- void onStart(@NonNull Fragment fragment, @NonNull CancellationSignal signal);
-
- /**
- * Called whenever an transition is complete
- */
- void onComplete(@NonNull Fragment fragment, @NonNull CancellationSignal signal);
- }
-
static final FragmentTransitionImpl PLATFORM_IMPL = Build.VERSION.SDK_INT >= 21
? new FragmentTransitionCompat21()
: null;
@@ -93,847 +49,6 @@
}
/**
- * The main entry point for Fragment Transitions, this starts the transitions
- * set on the leaving Fragment's {@link Fragment#getExitTransition()}, the
- * entering Fragment's {@link Fragment#getEnterTransition()} and
- * {@link Fragment#getSharedElementEnterTransition()}. When popping,
- * the leaving Fragment's {@link Fragment#getReturnTransition()} and
- * {@link Fragment#getSharedElementReturnTransition()} and the entering
- * {@link Fragment#getReenterTransition()} will be run.
- * <p>
- * With reordered Fragment Transitions, all Views have been added to the
- * View hierarchy prior to calling this method. The incoming Fragment's Views
- * will be INVISIBLE. With ordered Fragment Transitions, this method
- * is called before any change has been made to the hierarchy. That means
- * that the added Fragments have not created their Views yet and the hierarchy
- * is unknown.
- *
- * @param context The hosting context
- * @param fragmentContainer the FragmentContainer for finding the container for each Fragment
- * @param records The list of transactions being executed.
- * @param isRecordPop For each transaction, whether it is a pop transaction or not.
- * @param startIndex The first index into records and isRecordPop to execute as
- * part of this transition.
- * @param endIndex One past the last index into records and isRecordPop to execute
- * as part of this transition.
- * @param isReordered true if this is a reordered transaction, meaning that the
- * Views of incoming fragments have been added. false if the
- * transaction has yet to be run and Views haven't been created.
- */
- static void startTransitions(@NonNull Context context,
- @NonNull FragmentContainer fragmentContainer,
- ArrayList<BackStackRecord> records, ArrayList<Boolean> isRecordPop,
- int startIndex, int endIndex, boolean isReordered, Callback callback) {
- SparseArray<FragmentContainerTransition> transitioningFragments =
- new SparseArray<>();
- for (int i = startIndex; i < endIndex; i++) {
- final BackStackRecord record = records.get(i);
- final boolean isPop = isRecordPop.get(i);
- if (isPop) {
- calculatePopFragments(record, transitioningFragments, isReordered);
- } else {
- calculateFragments(record, transitioningFragments, isReordered);
- }
- }
-
- if (transitioningFragments.size() != 0) {
- final View nonExistentView = new View(context);
- final int numContainers = transitioningFragments.size();
- for (int i = 0; i < numContainers; i++) {
- int containerId = transitioningFragments.keyAt(i);
- ArrayMap<String, String> nameOverrides = calculateNameOverrides(containerId,
- records, isRecordPop, startIndex, endIndex);
-
- FragmentContainerTransition containerTransition =
- transitioningFragments.valueAt(i);
-
- if (fragmentContainer.onHasView()) {
- ViewGroup container = (ViewGroup) fragmentContainer.onFindViewById(
- containerId);
- if (container == null) {
- // No container means no transitions
- continue;
- }
- if (isReordered) {
- configureTransitionsReordered(container,
- containerTransition, nonExistentView, nameOverrides, callback);
- } else {
- configureTransitionsOrdered(container,
- containerTransition, nonExistentView, nameOverrides, callback);
- }
- }
- }
- }
- }
-
- /**
- * Iterates through the transactions that affect a given fragment container
- * and tracks the shared element names across transactions. This is most useful
- * in pop transactions where the names of shared elements are known.
- *
- * @param containerId The container ID that is executing the transition.
- * @param records The list of transactions being executed.
- * @param isRecordPop For each transaction, whether it is a pop transaction or not.
- * @param startIndex The first index into records and isRecordPop to execute as
- * part of this transition.
- * @param endIndex One past the last index into records and isRecordPop to execute
- * as part of this transition.
- * @return A map from the initial shared element name to the final shared element name
- * before any onMapSharedElements is run.
- */
- private static ArrayMap<String, String> calculateNameOverrides(int containerId,
- ArrayList<BackStackRecord> records, ArrayList<Boolean> isRecordPop,
- int startIndex, int endIndex) {
- ArrayMap<String, String> nameOverrides = new ArrayMap<>();
- for (int recordNum = endIndex - 1; recordNum >= startIndex; recordNum--) {
- final BackStackRecord record = records.get(recordNum);
- if (!record.interactsWith(containerId)) {
- continue;
- }
- final boolean isPop = isRecordPop.get(recordNum);
- if (record.mSharedElementSourceNames != null) {
- final int numSharedElements = record.mSharedElementSourceNames.size();
- final ArrayList<String> sources;
- final ArrayList<String> targets;
- if (isPop) {
- targets = record.mSharedElementSourceNames;
- sources = record.mSharedElementTargetNames;
- } else {
- sources = record.mSharedElementSourceNames;
- targets = record.mSharedElementTargetNames;
- }
- for (int i = 0; i < numSharedElements; i++) {
- String sourceName = sources.get(i);
- String targetName = targets.get(i);
- String previousTarget = nameOverrides.remove(targetName);
- if (previousTarget != null) {
- nameOverrides.put(sourceName, previousTarget);
- } else {
- nameOverrides.put(sourceName, targetName);
- }
- }
- }
- }
- return nameOverrides;
- }
-
- /**
- * Configures a transition for a single fragment container for which the transaction was
- * reordered. That means that all Fragment Views have been added and incoming fragment
- * Views are marked invisible.
- *
- * @param container the container that the transitioning fragments are within
- * @param fragments A structure holding the transitioning fragments in this container.
- * @param nonExistentView A View that does not exist in the hierarchy. This is used to
- * prevent transitions from acting on other Views when there is no
- * other target.
- * @param nameOverrides A map of the shared element names from the starting fragment to
- * the final fragment's Views as given in
- * {@link FragmentTransaction#addSharedElement(View, String)}.
- */
- private static void configureTransitionsReordered(@NonNull ViewGroup container,
- FragmentContainerTransition fragments,
- View nonExistentView, ArrayMap<String, String> nameOverrides, final Callback callback) {
- final Fragment inFragment = fragments.lastIn;
- final Fragment outFragment = fragments.firstOut;
- final FragmentTransitionImpl impl = chooseImpl(outFragment, inFragment);
- if (impl == null) {
- return;
- }
- final boolean inIsPop = fragments.lastInIsPop;
- final boolean outIsPop = fragments.firstOutIsPop;
-
- ArrayList<View> sharedElementsIn = new ArrayList<>();
- ArrayList<View> sharedElementsOut = new ArrayList<>();
- Object enterTransition = getEnterTransition(impl, inFragment, inIsPop);
- Object exitTransition = getExitTransition(impl, outFragment, outIsPop);
-
- Object sharedElementTransition = configureSharedElementsReordered(impl, container,
- nonExistentView, nameOverrides, fragments, sharedElementsOut, sharedElementsIn,
- enterTransition, exitTransition);
-
- if (enterTransition == null && sharedElementTransition == null
- && exitTransition == null) {
- return; // no transitions!
- }
-
- ArrayList<View> exitingViews = configureEnteringExitingViews(impl, exitTransition,
- outFragment, sharedElementsOut, nonExistentView);
-
- ArrayList<View> enteringViews = configureEnteringExitingViews(impl, enterTransition,
- inFragment, sharedElementsIn, nonExistentView);
-
- setViewVisibility(enteringViews, View.INVISIBLE);
-
- Object transition = mergeTransitions(impl, enterTransition, exitTransition,
- sharedElementTransition, inFragment, inIsPop);
-
- if (outFragment != null && exitingViews != null
- && (exitingViews.size() > 0 || sharedElementsOut.size() > 0)) {
- final CancellationSignal signal = new CancellationSignal();
- callback.onStart(outFragment, signal);
- impl.setListenerForTransitionEnd(outFragment, transition, signal, new Runnable() {
- @Override
- public void run() {
- callback.onComplete(outFragment, signal);
- }
- });
- }
-
- if (transition != null) {
- replaceHide(impl, exitTransition, outFragment, exitingViews);
- ArrayList<String> inNames =
- impl.prepareSetNameOverridesReordered(sharedElementsIn);
- impl.scheduleRemoveTargets(transition,
- enterTransition, enteringViews, exitTransition, exitingViews,
- sharedElementTransition, sharedElementsIn);
- impl.beginDelayedTransition(container, transition);
- impl.setNameOverridesReordered(container, sharedElementsOut,
- sharedElementsIn, inNames, nameOverrides);
- setViewVisibility(enteringViews, View.VISIBLE);
- impl.swapSharedElementTargets(sharedElementTransition,
- sharedElementsOut, sharedElementsIn);
- }
- }
-
- /**
- * Replace hide operations with visibility changes on the exiting views. Instead of making
- * the entire fragment's view GONE, make each exiting view INVISIBLE. At the end of the
- * transition, make the fragment's view GONE.
- */
- private static void replaceHide(FragmentTransitionImpl impl,
- Object exitTransition, Fragment exitingFragment,
- final ArrayList<View> exitingViews) {
- if (exitingFragment != null && exitTransition != null && exitingFragment.mAdded
- && exitingFragment.mHidden && exitingFragment.mHiddenChanged) {
- exitingFragment.setHideReplaced(true);
- impl.scheduleHideFragmentView(exitTransition,
- exitingFragment.getView(), exitingViews);
- /* This is required to indicate to the TransitionManager the desired end state of the
- scene when a hide is used. In the replace case, the exiting Fragment's view is
- removed from the sceneRoot during the delay, and the TransitionManager is able to
- calculate the difference between the two switching views. Because we can have
- exiting child views with transitions, we cannot just mark the entire exiting
- Fragment view as INVISIBLE or the TransitionManager will not consider the views
- individually.
-
- This OneShotPreDrawListener gets fired before the delayed start of the Transition and
- changes the visibility of any exiting child views that *ARE NOT* shared element
- transitions. The TransitionManager then properly considers exiting views and marks
- them as disappearing, applying a transition and a listener to take proper actions
- once the transition is complete.
- */
-
- final ViewGroup container = exitingFragment.mContainer;
- OneShotPreDrawListener.add(container, new Runnable() {
- @Override
- public void run() {
- setViewVisibility(exitingViews, View.INVISIBLE);
- }
- });
- }
- }
-
- /**
- * Configures a transition for a single fragment container for which the transaction was
- * ordered. That means that the transaction has not been executed yet, so incoming
- * Views are not yet known.
- *
- * @param container the container that the transitioning fragments are within
- * @param fragments A structure holding the transitioning fragments in this container.
- * @param nonExistentView A View that does not exist in the hierarchy. This is used to
- * prevent transitions from acting on other Views when there is no
- * other target.
- * @param nameOverrides A map of the shared element names from the starting fragment to
- * the final fragment's Views as given in
- * {@link FragmentTransaction#addSharedElement(View, String)}.
- */
- private static void configureTransitionsOrdered(@NonNull ViewGroup container,
- FragmentContainerTransition fragments,
- View nonExistentView, ArrayMap<String, String> nameOverrides, final Callback callback) {
- final Fragment inFragment = fragments.lastIn;
- final Fragment outFragment = fragments.firstOut;
- final FragmentTransitionImpl impl = chooseImpl(outFragment, inFragment);
- if (impl == null) {
- return;
- }
- final boolean inIsPop = fragments.lastInIsPop;
- final boolean outIsPop = fragments.firstOutIsPop;
-
- Object enterTransition = getEnterTransition(impl, inFragment, inIsPop);
- Object exitTransition = getExitTransition(impl, outFragment, outIsPop);
-
- ArrayList<View> sharedElementsOut = new ArrayList<>();
- ArrayList<View> sharedElementsIn = new ArrayList<>();
-
- Object sharedElementTransition = configureSharedElementsOrdered(impl, container,
- nonExistentView, nameOverrides, fragments, sharedElementsOut, sharedElementsIn,
- enterTransition, exitTransition);
-
- if (enterTransition == null && sharedElementTransition == null
- && exitTransition == null) {
- return; // no transitions!
- }
-
- ArrayList<View> exitingViews = configureEnteringExitingViews(impl, exitTransition,
- outFragment, sharedElementsOut, nonExistentView);
-
- if (exitingViews == null || exitingViews.isEmpty()) {
- exitTransition = null;
- }
-
- // Ensure the entering transition doesn't target anything until the views are made
- // visible
- impl.addTarget(enterTransition, nonExistentView);
-
- Object transition = mergeTransitions(impl, enterTransition, exitTransition,
- sharedElementTransition, inFragment, fragments.lastInIsPop);
-
- if (outFragment != null && exitingViews != null
- && (exitingViews.size() > 0 || sharedElementsOut.size() > 0)) {
- final CancellationSignal signal = new CancellationSignal();
- callback.onStart(outFragment, signal);
- impl.setListenerForTransitionEnd(outFragment, transition, signal, new Runnable() {
- @Override
- public void run() {
- callback.onComplete(outFragment, signal);
- }
- });
- }
-
- if (transition != null) {
- final ArrayList<View> enteringViews = new ArrayList<>();
- impl.scheduleRemoveTargets(transition,
- enterTransition, enteringViews, exitTransition, exitingViews,
- sharedElementTransition, sharedElementsIn);
- scheduleTargetChange(impl, container, inFragment, nonExistentView, sharedElementsIn,
- enterTransition, enteringViews, exitTransition, exitingViews);
- impl.setNameOverridesOrdered(container, sharedElementsIn, nameOverrides);
-
- impl.beginDelayedTransition(container, transition);
- impl.scheduleNameReset(container, sharedElementsIn, nameOverrides);
- }
- }
-
- /**
- * This method is used for fragment transitions for ordrered transactions to change the
- * enter and exit transition targets after the call to
- * {@link FragmentTransitionCompat21#beginDelayedTransition(ViewGroup, Object)}. The exit
- * transition must ensure that it does not target any Views and the enter transition must start
- * targeting the Views of the incoming Fragment.
- *
- * @param sceneRoot The fragment container View
- * @param inFragment The last fragment that is entering
- * @param nonExistentView A view that does not exist in the hierarchy that is used as a
- * transition target to ensure no View is targeted.
- * @param sharedElementsIn The shared element Views of the incoming fragment
- * @param enterTransition The enter transition of the incoming fragment
- * @param enteringViews The entering Views of the incoming fragment
- * @param exitTransition The exit transition of the outgoing fragment
- * @param exitingViews The exiting views of the outgoing fragment
- */
- private static void scheduleTargetChange(final FragmentTransitionImpl impl,
- final ViewGroup sceneRoot,
- final Fragment inFragment, final View nonExistentView,
- final ArrayList<View> sharedElementsIn,
- final Object enterTransition, final ArrayList<View> enteringViews,
- final Object exitTransition, final ArrayList<View> exitingViews) {
- OneShotPreDrawListener.add(sceneRoot, new Runnable() {
- @Override
- public void run() {
- if (enterTransition != null) {
- impl.removeTarget(enterTransition,
- nonExistentView);
- ArrayList<View> views = configureEnteringExitingViews(impl,
- enterTransition, inFragment, sharedElementsIn, nonExistentView);
- enteringViews.addAll(views);
- }
-
- if (exitingViews != null) {
- if (exitTransition != null) {
- ArrayList<View> tempExiting = new ArrayList<>();
- tempExiting.add(nonExistentView);
- impl.replaceTargets(exitTransition, exitingViews,
- tempExiting);
- }
- exitingViews.clear();
- exitingViews.add(nonExistentView);
- }
- }
- });
- }
-
- /**
- * Chooses the appropriate implementation depending on the Transition instances hold by the
- * Fragments.
- */
- private static FragmentTransitionImpl chooseImpl(Fragment outFragment, Fragment inFragment) {
- // Collect all transition instances
- final ArrayList<Object> transitions = new ArrayList<>();
- if (outFragment != null) {
- final Object exitTransition = outFragment.getExitTransition();
- if (exitTransition != null) {
- transitions.add(exitTransition);
- }
- final Object returnTransition = outFragment.getReturnTransition();
- if (returnTransition != null) {
- transitions.add(returnTransition);
- }
- final Object sharedReturnTransition = outFragment.getSharedElementReturnTransition();
- if (sharedReturnTransition != null) {
- transitions.add(sharedReturnTransition);
- }
- }
- if (inFragment != null) {
- final Object enterTransition = inFragment.getEnterTransition();
- if (enterTransition != null) {
- transitions.add(enterTransition);
- }
- final Object reenterTransition = inFragment.getReenterTransition();
- if (reenterTransition != null) {
- transitions.add(reenterTransition);
- }
- final Object sharedEnterTransition = inFragment.getSharedElementEnterTransition();
- if (sharedEnterTransition != null) {
- transitions.add(sharedEnterTransition);
- }
- }
- if (transitions.isEmpty()) {
- return null; // No transition to run
- }
- // Pick the implementation that can handle all the transitions
- if (PLATFORM_IMPL != null && canHandleAll(PLATFORM_IMPL, transitions)) {
- return PLATFORM_IMPL;
- }
- if (SUPPORT_IMPL != null && canHandleAll(SUPPORT_IMPL, transitions)) {
- return SUPPORT_IMPL;
- }
- if (PLATFORM_IMPL != null || SUPPORT_IMPL != null) {
- throw new IllegalArgumentException("Invalid Transition types");
- }
- return null;
- }
-
- private static boolean canHandleAll(FragmentTransitionImpl impl, List<Object> transitions) {
- for (int i = 0, size = transitions.size(); i < size; i++) {
- if (!impl.canHandle(transitions.get(i))) {
- return false;
- }
- }
- return true;
- }
-
- /**
- * Returns a TransitionSet containing the shared element transition. The wrapping TransitionSet
- * targets all shared elements to ensure that no other Views are targeted. The shared element
- * transition can then target any or all shared elements without worrying about accidentally
- * targeting entering or exiting Views.
- *
- * @param inFragment The incoming fragment
- * @param outFragment the outgoing fragment
- * @param isPop True if this is a pop transaction or false if it is a normal (add) transaction.
- * @return A TransitionSet wrapping the shared element transition or null if no such transition
- * exists.
- */
- private static Object getSharedElementTransition(FragmentTransitionImpl impl,
- Fragment inFragment, Fragment outFragment, boolean isPop) {
- if (inFragment == null || outFragment == null) {
- return null;
- }
- Object transition = impl.cloneTransition(isPop
- ? outFragment.getSharedElementReturnTransition()
- : inFragment.getSharedElementEnterTransition());
- return impl.wrapTransitionInSet(transition);
- }
-
- /**
- * Returns a clone of the enter transition or null if no such transition exists.
- */
- private static Object getEnterTransition(FragmentTransitionImpl impl,
- Fragment inFragment, boolean isPop) {
- if (inFragment == null) {
- return null;
- }
- return impl.cloneTransition(isPop
- ? inFragment.getReenterTransition()
- : inFragment.getEnterTransition());
- }
-
- /**
- * Returns a clone of the exit transition or null if no such transition exists.
- */
- private static Object getExitTransition(FragmentTransitionImpl impl,
- Fragment outFragment, boolean isPop) {
- if (outFragment == null) {
- return null;
- }
- return impl.cloneTransition(isPop
- ? outFragment.getReturnTransition()
- : outFragment.getExitTransition());
- }
-
- /**
- * Configures the shared elements of a reordered fragment transaction's transition.
- * This retrieves the shared elements of the outgoing and incoming fragments, maps the
- * views, and sets up the epicenter on the transitions.
- * <p>
- * The epicenter of exit and shared element transitions is the first shared element
- * in the outgoing fragment. The epicenter of the entering transition is the first shared
- * element in the incoming fragment.
- *
- * @param sceneRoot The fragment container View
- * @param nonExistentView A View that does not exist in the hierarchy. This is used to
- * prevent transitions from acting on other Views when there is no
- * other target.
- * @param nameOverrides A map of the shared element names from the starting fragment to
- * the final fragment's Views as given in
- * {@link FragmentTransaction#addSharedElement(View, String)}.
- * @param fragments A structure holding the transitioning fragments in this container.
- * @param sharedElementsOut A list modified to contain the shared elements in the outgoing
- * fragment
- * @param sharedElementsIn A list modified to contain the shared elements in the incoming
- * fragment
- * @param enterTransition The transition used for entering Views, modified by applying the
- * epicenter
- * @param exitTransition The transition used for exiting Views, modified by applying the
- * epicenter
- * @return The shared element transition or null if no shared elements exist
- */
- private static Object configureSharedElementsReordered(final FragmentTransitionImpl impl,
- final ViewGroup sceneRoot,
- final View nonExistentView, final ArrayMap<String, String> nameOverrides,
- final FragmentContainerTransition fragments,
- final ArrayList<View> sharedElementsOut,
- final ArrayList<View> sharedElementsIn,
- final Object enterTransition, final Object exitTransition) {
- final Fragment inFragment = fragments.lastIn;
- final Fragment outFragment = fragments.firstOut;
- if (inFragment != null) {
- inFragment.requireView().setVisibility(View.VISIBLE);
- }
- if (inFragment == null || outFragment == null) {
- return null; // no shared element without a fragment
- }
-
- final boolean inIsPop = fragments.lastInIsPop;
- Object sharedElementTransition = nameOverrides.isEmpty() ? null
- : getSharedElementTransition(impl, inFragment, outFragment, inIsPop);
-
- final ArrayMap<String, View> outSharedElements = captureOutSharedElements(impl,
- nameOverrides, sharedElementTransition, fragments);
-
- final ArrayMap<String, View> inSharedElements = captureInSharedElements(impl,
- nameOverrides, sharedElementTransition, fragments);
-
- if (nameOverrides.isEmpty()) {
- sharedElementTransition = null;
- if (outSharedElements != null) {
- outSharedElements.clear();
- }
- if (inSharedElements != null) {
- inSharedElements.clear();
- }
- } else {
- addSharedElementsWithMatchingNames(sharedElementsOut, outSharedElements,
- nameOverrides.keySet());
- addSharedElementsWithMatchingNames(sharedElementsIn, inSharedElements,
- nameOverrides.values());
- }
-
- if (enterTransition == null && exitTransition == null && sharedElementTransition == null) {
- // don't call onSharedElementStart/End since there is no transition
- return null;
- }
-
- callSharedElementStartEnd(inFragment, outFragment, inIsPop, outSharedElements, true);
-
- final Rect epicenter;
- final View epicenterView;
- if (sharedElementTransition != null) {
- sharedElementsIn.add(nonExistentView);
- impl.setSharedElementTargets(sharedElementTransition,
- nonExistentView, sharedElementsOut);
- final boolean outIsPop = fragments.firstOutIsPop;
- final BackStackRecord outTransaction = fragments.firstOutTransaction;
- setOutEpicenter(impl, sharedElementTransition, exitTransition, outSharedElements,
- outIsPop, outTransaction);
- epicenter = new Rect();
- epicenterView = getInEpicenterView(inSharedElements, fragments,
- enterTransition, inIsPop);
- if (epicenterView != null) {
- impl.setEpicenter(enterTransition, epicenter);
- }
- } else {
- epicenter = null;
- epicenterView = null;
- }
-
- OneShotPreDrawListener.add(sceneRoot, new Runnable() {
- @Override
- public void run() {
- callSharedElementStartEnd(inFragment, outFragment, inIsPop,
- inSharedElements, false);
- if (epicenterView != null) {
- impl.getBoundsOnScreen(epicenterView, epicenter);
- }
- }
- });
- return sharedElementTransition;
- }
-
- /**
- * Add Views from sharedElements into views that have the transitionName in the
- * nameOverridesSet.
- *
- * @param views Views list to add shared elements to
- * @param sharedElements List of shared elements
- * @param nameOverridesSet The transition names for all views to be copied from
- * sharedElements to views.
- */
- private static void addSharedElementsWithMatchingNames(ArrayList<View> views,
- ArrayMap<String, View> sharedElements, Collection<String> nameOverridesSet) {
- for (int i = sharedElements.size() - 1; i >= 0; i--) {
- View view = sharedElements.valueAt(i);
- if (nameOverridesSet.contains(ViewCompat.getTransitionName(view))) {
- views.add(view);
- }
- }
- }
-
- /**
- * Configures the shared elements of an ordered fragment transaction's transition.
- * This retrieves the shared elements of the incoming fragments, and schedules capturing
- * the incoming fragment's shared elements. It also maps the views, and sets up the epicenter
- * on the transitions.
- * <p>
- * The epicenter of exit and shared element transitions is the first shared element
- * in the outgoing fragment. The epicenter of the entering transition is the first shared
- * element in the incoming fragment.
- *
- * @param sceneRoot The fragment container View
- * @param nonExistentView A View that does not exist in the hierarchy. This is used to
- * prevent transitions from acting on other Views when there is no
- * other target.
- * @param nameOverrides A map of the shared element names from the starting fragment to
- * the final fragment's Views as given in
- * {@link FragmentTransaction#addSharedElement(View, String)}.
- * @param fragments A structure holding the transitioning fragments in this container.
- * @param sharedElementsOut A list modified to contain the shared elements in the outgoing
- * fragment
- * @param sharedElementsIn A list modified to contain the shared elements in the incoming
- * fragment
- * @param enterTransition The transition used for entering Views, modified by applying the
- * epicenter
- * @param exitTransition The transition used for exiting Views, modified by applying the
- * epicenter
- * @return The shared element transition or null if no shared elements exist
- */
- private static Object configureSharedElementsOrdered(final FragmentTransitionImpl impl,
- final ViewGroup sceneRoot,
- final View nonExistentView, final ArrayMap<String, String> nameOverrides,
- final FragmentContainerTransition fragments,
- final ArrayList<View> sharedElementsOut,
- final ArrayList<View> sharedElementsIn,
- final Object enterTransition, final Object exitTransition) {
- final Fragment inFragment = fragments.lastIn;
- final Fragment outFragment = fragments.firstOut;
-
- if (inFragment == null || outFragment == null) {
- return null; // no transition
- }
-
- final boolean inIsPop = fragments.lastInIsPop;
- Object sharedElementTransition = nameOverrides.isEmpty() ? null
- : getSharedElementTransition(impl, inFragment, outFragment, inIsPop);
-
- ArrayMap<String, View> outSharedElements = captureOutSharedElements(impl, nameOverrides,
- sharedElementTransition, fragments);
-
- if (nameOverrides.isEmpty()) {
- sharedElementTransition = null;
- } else {
- sharedElementsOut.addAll(outSharedElements.values());
- }
-
- if (enterTransition == null && exitTransition == null && sharedElementTransition == null) {
- // don't call onSharedElementStart/End since there is no transition
- return null;
- }
-
- callSharedElementStartEnd(inFragment, outFragment, inIsPop, outSharedElements, true);
-
- final Rect inEpicenter;
- if (sharedElementTransition != null) {
- inEpicenter = new Rect();
- impl.setSharedElementTargets(sharedElementTransition,
- nonExistentView, sharedElementsOut);
- final boolean outIsPop = fragments.firstOutIsPop;
- final BackStackRecord outTransaction = fragments.firstOutTransaction;
- setOutEpicenter(impl, sharedElementTransition, exitTransition, outSharedElements,
- outIsPop, outTransaction);
- if (enterTransition != null) {
- impl.setEpicenter(enterTransition, inEpicenter);
- }
- } else {
- inEpicenter = null;
- }
-
-
- final Object finalSharedElementTransition = sharedElementTransition;
- OneShotPreDrawListener.add(sceneRoot, new Runnable() {
- @Override
- public void run() {
- ArrayMap<String, View> inSharedElements = captureInSharedElements(impl,
- nameOverrides, finalSharedElementTransition, fragments);
-
- if (inSharedElements != null) {
- sharedElementsIn.addAll(inSharedElements.values());
- sharedElementsIn.add(nonExistentView);
- }
-
- callSharedElementStartEnd(inFragment, outFragment, inIsPop,
- inSharedElements, false);
- if (finalSharedElementTransition != null) {
- impl.swapSharedElementTargets(
- finalSharedElementTransition, sharedElementsOut,
- sharedElementsIn);
-
- final View inEpicenterView = getInEpicenterView(inSharedElements,
- fragments, enterTransition, inIsPop);
- if (inEpicenterView != null) {
- impl.getBoundsOnScreen(inEpicenterView,
- inEpicenter);
- }
- }
- }
- });
-
- return sharedElementTransition;
- }
-
- /**
- * Finds the shared elements in the outgoing fragment. It also calls
- * {@link SharedElementCallback#onMapSharedElements(List, Map)} to allow more control
- * of the shared element mapping. {@code nameOverrides} is updated to match the
- * actual transition name of the mapped shared elements.
- *
- * @param nameOverrides A map of the shared element names from the starting fragment to
- * the final fragment's Views as given in
- * {@link FragmentTransaction#addSharedElement(View, String)}.
- * @param sharedElementTransition The shared element transition
- * @param fragments A structure holding the transitioning fragments in this container.
- * @return The mapping of shared element names to the Views in the hierarchy or null
- * if there is no shared element transition.
- */
- private static ArrayMap<String, View> captureOutSharedElements(FragmentTransitionImpl impl,
- ArrayMap<String, String> nameOverrides, Object sharedElementTransition,
- FragmentContainerTransition fragments) {
- if (nameOverrides.isEmpty() || sharedElementTransition == null) {
- nameOverrides.clear();
- return null;
- }
- final Fragment outFragment = fragments.firstOut;
- final ArrayMap<String, View> outSharedElements = new ArrayMap<>();
- impl.findNamedViews(outSharedElements, outFragment.requireView());
-
- final SharedElementCallback sharedElementCallback;
- final ArrayList<String> names;
- final BackStackRecord outTransaction = fragments.firstOutTransaction;
- if (fragments.firstOutIsPop) {
- sharedElementCallback = outFragment.getEnterTransitionCallback();
- names = outTransaction.mSharedElementTargetNames;
- } else {
- sharedElementCallback = outFragment.getExitTransitionCallback();
- names = outTransaction.mSharedElementSourceNames;
- }
-
- if (names != null) {
- outSharedElements.retainAll(names);
- }
- if (sharedElementCallback != null) {
- sharedElementCallback.onMapSharedElements(names, outSharedElements);
- for (int i = names.size() - 1; i >= 0; i--) {
- String name = names.get(i);
- View view = outSharedElements.get(name);
- if (view == null) {
- nameOverrides.remove(name);
- } else if (!name.equals(ViewCompat.getTransitionName(view))) {
- String targetValue = nameOverrides.remove(name);
- nameOverrides.put(ViewCompat.getTransitionName(view), targetValue);
- }
- }
- } else {
- nameOverrides.retainAll(outSharedElements.keySet());
- }
- return outSharedElements;
- }
-
- /**
- * Finds the shared elements in the incoming fragment. It also calls
- * {@link SharedElementCallback#onMapSharedElements(List, Map)} to allow more control
- * of the shared element mapping. {@code nameOverrides} is updated to match the
- * actual transition name of the mapped shared elements.
- *
- * @param nameOverrides A map of the shared element names from the starting fragment to
- * the final fragment's Views as given in
- * {@link FragmentTransaction#addSharedElement(View, String)}.
- * @param sharedElementTransition The shared element transition
- * @param fragments A structure holding the transitioning fragments in this container.
- * @return The mapping of shared element names to the Views in the hierarchy or null
- * if there is no shared element transition.
- */
- @SuppressWarnings("WeakerAccess") /* synthetic access */
- static ArrayMap<String, View> captureInSharedElements(FragmentTransitionImpl impl,
- ArrayMap<String, String> nameOverrides, Object sharedElementTransition,
- FragmentContainerTransition fragments) {
- Fragment inFragment = fragments.lastIn;
- final View fragmentView = inFragment.getView();
- if (nameOverrides.isEmpty() || sharedElementTransition == null || fragmentView == null) {
- nameOverrides.clear();
- return null;
- }
- final ArrayMap<String, View> inSharedElements = new ArrayMap<>();
- impl.findNamedViews(inSharedElements, fragmentView);
-
- final SharedElementCallback sharedElementCallback;
- final ArrayList<String> names;
- final BackStackRecord inTransaction = fragments.lastInTransaction;
- if (fragments.lastInIsPop) {
- sharedElementCallback = inFragment.getExitTransitionCallback();
- names = inTransaction.mSharedElementSourceNames;
- } else {
- sharedElementCallback = inFragment.getEnterTransitionCallback();
- names = inTransaction.mSharedElementTargetNames;
- }
-
- if (names != null) {
- inSharedElements.retainAll(names);
- inSharedElements.retainAll(nameOverrides.values());
- }
- if (sharedElementCallback != null) {
- sharedElementCallback.onMapSharedElements(names, inSharedElements);
- for (int i = names.size() - 1; i >= 0; i--) {
- String name = names.get(i);
- View view = inSharedElements.get(name);
- if (view == null) {
- String key = findKeyForValue(nameOverrides, name);
- if (key != null) {
- nameOverrides.remove(key);
- }
- } else if (!name.equals(ViewCompat.getTransitionName(view))) {
- String key = findKeyForValue(nameOverrides, name);
- if (key != null) {
- nameOverrides.put(key, ViewCompat.getTransitionName(view));
- }
- }
- }
- } else {
- retainValues(nameOverrides, inSharedElements);
- }
- return inSharedElements;
- }
-
- /**
* Utility to find the String key in {@code map} that maps to {@code value}.
*/
static String findKeyForValue(ArrayMap<String, String> map, String value) {
@@ -947,57 +62,6 @@
}
/**
- * Returns the View in the incoming Fragment that should be used as the epicenter.
- *
- * @param inSharedElements The mapping of shared element names to Views in the
- * incoming fragment.
- * @param fragments A structure holding the transitioning fragments in this container.
- * @param enterTransition The transition used for the incoming Fragment's views
- * @param inIsPop Is the incoming fragment being added as a pop transaction?
- */
- @SuppressWarnings("WeakerAccess") /* synthetic access */
- static View getInEpicenterView(ArrayMap<String, View> inSharedElements,
- FragmentContainerTransition fragments,
- Object enterTransition, boolean inIsPop) {
- BackStackRecord inTransaction = fragments.lastInTransaction;
- if (enterTransition != null && inSharedElements != null
- && inTransaction.mSharedElementSourceNames != null
- && !inTransaction.mSharedElementSourceNames.isEmpty()) {
- final String targetName = inIsPop
- ? inTransaction.mSharedElementSourceNames.get(0)
- : inTransaction.mSharedElementTargetNames.get(0);
- return inSharedElements.get(targetName);
- }
- return null;
- }
-
- /**
- * Sets the epicenter for the exit transition.
- *
- * @param sharedElementTransition The shared element transition
- * @param exitTransition The transition for the outgoing fragment's views
- * @param outSharedElements Shared elements in the outgoing fragment
- * @param outIsPop Is the outgoing fragment being removed as a pop transaction?
- * @param outTransaction The transaction that caused the fragment to be removed.
- */
- private static void setOutEpicenter(FragmentTransitionImpl impl, Object sharedElementTransition,
- Object exitTransition, ArrayMap<String, View> outSharedElements, boolean outIsPop,
- BackStackRecord outTransaction) {
- if (outTransaction.mSharedElementSourceNames != null
- && !outTransaction.mSharedElementSourceNames.isEmpty()) {
- final String sourceName = outIsPop
- ? outTransaction.mSharedElementTargetNames.get(0)
- : outTransaction.mSharedElementSourceNames.get(0);
- final View outEpicenterView = outSharedElements.get(sourceName);
- impl.setEpicenter(sharedElementTransition, outEpicenterView);
-
- if (exitTransition != null) {
- impl.setEpicenter(exitTransition, outEpicenterView);
- }
- }
- }
-
- /**
* A utility to retain only the mappings in {@code nameOverrides} that have a value
* that has a key in {@code namedViews}. This is a useful equivalent to
* {@link ArrayMap#retainAll(Collection)} for values.
@@ -1045,28 +109,6 @@
}
}
- @SuppressWarnings("WeakerAccess") /* synthetic access */
- static ArrayList<View> configureEnteringExitingViews(FragmentTransitionImpl impl,
- Object transition,
- Fragment fragment, ArrayList<View> sharedElements, View nonExistentView) {
- ArrayList<View> viewList = null;
- if (transition != null) {
- viewList = new ArrayList<>();
- View root = fragment.getView();
- if (root != null) {
- impl.captureTransitioningViews(viewList, root);
- }
- if (sharedElements != null) {
- viewList.removeAll(sharedElements);
- }
- if (!viewList.isEmpty()) {
- viewList.add(nonExistentView);
- impl.addTargets(transition, viewList);
- }
- }
- return viewList;
- }
-
/**
* Sets the visibility of all Views in {@code views} to {@code visibility}.
*/
@@ -1080,237 +122,10 @@
}
}
- /**
- * Merges exit, shared element, and enter transitions so that they act together or
- * sequentially as defined in the fragments.
- */
- private static Object mergeTransitions(FragmentTransitionImpl impl, Object enterTransition,
- Object exitTransition, Object sharedElementTransition, Fragment inFragment,
- boolean isPop) {
- boolean overlap = true;
- if (enterTransition != null && exitTransition != null && inFragment != null) {
- overlap = isPop ? inFragment.getAllowReturnTransitionOverlap() :
- inFragment.getAllowEnterTransitionOverlap();
- }
-
- // Wrap the transitions. Explicit targets like in enter and exit will cause the
- // views to be targeted regardless of excluded views. If that happens, then the
- // excluded fragments views (hidden fragments) will still be in the transition.
-
- Object transition;
- if (overlap) {
- // Regular transition -- do it all together
- transition = impl.mergeTransitionsTogether(exitTransition,
- enterTransition, sharedElementTransition);
- } else {
- // First do exit, then enter, but allow shared element transition to happen
- // during both.
- transition = impl.mergeTransitionsInSequence(exitTransition,
- enterTransition, sharedElementTransition);
- }
- return transition;
- }
-
- /**
- * Finds the first removed fragment and last added fragments when going forward.
- * If none of the fragments have transitions, then both lists will be empty.
- *
- * @param transitioningFragments Keyed on the container ID, the first fragments to be removed,
- * and last fragments to be added. This will be modified by
- * this method.
- */
- public static void calculateFragments(BackStackRecord transaction,
- SparseArray<FragmentContainerTransition> transitioningFragments,
- boolean isReordered) {
- final int numOps = transaction.mOps.size();
- for (int opNum = 0; opNum < numOps; opNum++) {
- final BackStackRecord.Op op = transaction.mOps.get(opNum);
- addToFirstInLastOut(transaction, op, transitioningFragments, false, isReordered);
- }
- }
-
- /**
- * Finds the first removed fragment and last added fragments when popping the back stack.
- * If none of the fragments have transitions, then both lists will be empty.
- *
- * @param transitioningFragments Keyed on the container ID, the first fragments to be removed,
- * and last fragments to be added. This will be modified by
- * this method.
- */
- public static void calculatePopFragments(BackStackRecord transaction,
- SparseArray<FragmentContainerTransition> transitioningFragments, boolean isReordered) {
- if (!transaction.mManager.getContainer().onHasView()) {
- return; // nothing to see, so no transitions
- }
- final int numOps = transaction.mOps.size();
- for (int opNum = numOps - 1; opNum >= 0; opNum--) {
- final BackStackRecord.Op op = transaction.mOps.get(opNum);
- addToFirstInLastOut(transaction, op, transitioningFragments, true, isReordered);
- }
- }
-
static boolean supportsTransition() {
return PLATFORM_IMPL != null || SUPPORT_IMPL != null;
}
- /**
- * Examines the {@code command} and may set the first out or last in fragment for the fragment's
- * container.
- *
- * @param transaction The executing transaction
- * @param op The operation being run.
- * @param transitioningFragments A structure holding the first in and last out fragments
- * for each fragment container.
- * @param isPop Is the operation a pop?
- * @param isReorderedTransaction True if the operations have been partially executed and the
- * added fragments have Views in the hierarchy or false if the
- * operations haven't been executed yet.
- */
- @SuppressWarnings("ReferenceEquality")
- private static void addToFirstInLastOut(BackStackRecord transaction, BackStackRecord.Op op,
- SparseArray<FragmentContainerTransition> transitioningFragments, boolean isPop,
- boolean isReorderedTransaction) {
- final Fragment fragment = op.mFragment;
- if (fragment == null) {
- return; // no fragment, no transition
- }
- final int containerId = fragment.mContainerId;
- if (containerId == 0) {
- return; // no container, no transition
- }
- final int command = isPop ? INVERSE_OPS[op.mCmd] : op.mCmd;
- boolean setLastIn = false;
- boolean wasRemoved = false;
- boolean setFirstOut = false;
- boolean wasAdded = false;
- switch (command) {
- case BackStackRecord.OP_SHOW:
- if (isReorderedTransaction) {
- setLastIn = fragment.mHiddenChanged && !fragment.mHidden && fragment.mAdded;
- } else {
- setLastIn = fragment.mHidden;
- }
- wasAdded = true;
- break;
- case BackStackRecord.OP_ADD:
- case BackStackRecord.OP_ATTACH:
- if (isReorderedTransaction) {
- setLastIn = fragment.mIsNewlyAdded;
- } else {
- setLastIn = !fragment.mAdded && !fragment.mHidden;
- }
- wasAdded = true;
- break;
- case BackStackRecord.OP_HIDE:
- if (isReorderedTransaction) {
- setFirstOut = fragment.mHiddenChanged && fragment.mAdded && fragment.mHidden;
- } else {
- setFirstOut = fragment.mAdded && !fragment.mHidden;
- }
- wasRemoved = true;
- break;
- case BackStackRecord.OP_REMOVE:
- case BackStackRecord.OP_DETACH:
- if (isReorderedTransaction) {
- setFirstOut = !fragment.mAdded && fragment.mView != null
- && fragment.mView.getVisibility() == View.VISIBLE
- && fragment.mPostponedAlpha >= 0;
- } else {
- setFirstOut = fragment.mAdded && !fragment.mHidden;
- }
- wasRemoved = true;
- break;
- }
- FragmentContainerTransition containerTransition = transitioningFragments.get(containerId);
- if (setLastIn) {
- containerTransition =
- ensureContainer(containerTransition, transitioningFragments, containerId);
- containerTransition.lastIn = fragment;
- containerTransition.lastInIsPop = isPop;
- containerTransition.lastInTransaction = transaction;
- }
- if (!isReorderedTransaction && wasAdded) {
- if (containerTransition != null && containerTransition.firstOut == fragment) {
- containerTransition.firstOut = null;
- }
-
- if (!transaction.mReorderingAllowed) {
- // When reordering isn't allowed, we may be starting Transitions before
- // the Fragment operation is actually executed so we move any new Fragments
- // to created here first so that they have a Context, etc. when they
- // are asked to load their Transitions
- FragmentManager manager = transaction.mManager;
- FragmentStateManager fragmentStateManager =
- manager.createOrGetFragmentStateManager(fragment);
- manager.getFragmentStore().makeActive(fragmentStateManager);
- manager.moveToState(fragment);
- }
- }
- if (setFirstOut && (containerTransition == null || containerTransition.firstOut == null)) {
- containerTransition =
- ensureContainer(containerTransition, transitioningFragments, containerId);
- containerTransition.firstOut = fragment;
- containerTransition.firstOutIsPop = isPop;
- containerTransition.firstOutTransaction = transaction;
- }
-
- if (!isReorderedTransaction && wasRemoved
- && (containerTransition != null && containerTransition.lastIn == fragment)) {
- containerTransition.lastIn = null;
- }
- }
-
- /**
- * Ensures that a FragmentContainerTransition has been added to the SparseArray. If so,
- * it returns the existing one. If not, one is created and added to the SparseArray and
- * returned.
- */
- private static FragmentContainerTransition ensureContainer(
- FragmentContainerTransition containerTransition,
- SparseArray<FragmentContainerTransition> transitioningFragments, int containerId) {
- if (containerTransition == null) {
- containerTransition = new FragmentContainerTransition();
- transitioningFragments.put(containerId, containerTransition);
- }
- return containerTransition;
- }
-
- /**
- * Tracks the last fragment added and first fragment removed for fragment transitions.
- * This also tracks which fragments are changed by push or pop transactions.
- */
- static class FragmentContainerTransition {
- /**
- * The last fragment added/attached/shown in its container
- */
- public Fragment lastIn;
-
- /**
- * true when lastIn was added during a pop transaction or false if added with a push
- */
- public boolean lastInIsPop;
-
- /**
- * The transaction that included the last in fragment
- */
- public BackStackRecord lastInTransaction;
-
- /**
- * The first fragment with a View that was removed/detached/hidden in its container.
- */
- public Fragment firstOut;
-
- /**
- * true when firstOut was removed during a pop transaction or false otherwise
- */
- public boolean firstOutIsPop;
-
- /**
- * The transaction that included the first out fragment
- */
- public BackStackRecord firstOutTransaction;
- }
-
private FragmentTransition() {
}
}
diff --git a/fragment/fragment/src/main/java/androidx/fragment/app/FragmentTransitionImpl.java b/fragment/fragment/src/main/java/androidx/fragment/app/FragmentTransitionImpl.java
index 4e4a7f7..651df8b 100644
--- a/fragment/fragment/src/main/java/androidx/fragment/app/FragmentTransitionImpl.java
+++ b/fragment/fragment/src/main/java/androidx/fragment/app/FragmentTransitionImpl.java
@@ -30,7 +30,6 @@
import androidx.core.os.CancellationSignal;
import androidx.core.view.OneShotPreDrawListener;
import androidx.core.view.ViewCompat;
-import androidx.core.view.ViewGroupCompat;
import java.util.ArrayList;
import java.util.List;
@@ -208,75 +207,6 @@
}
/**
- * Gets the Views in the hierarchy affected by entering and exiting Activity Scene transitions.
- *
- * @param transitioningViews This View will be added to transitioningViews if it is VISIBLE and
- * a normal View or a ViewGroup with
- * {@link android.view.ViewGroup#isTransitionGroup()} true.
- * @param view The base of the view hierarchy to look in.
- */
- void captureTransitioningViews(ArrayList<View> transitioningViews, View view) {
- if (view.getVisibility() == View.VISIBLE) {
- if (view instanceof ViewGroup) {
- ViewGroup viewGroup = (ViewGroup) view;
- if (ViewGroupCompat.isTransitionGroup(viewGroup)) {
- transitioningViews.add(viewGroup);
- } else {
- int count = viewGroup.getChildCount();
- for (int i = 0; i < count; i++) {
- View child = viewGroup.getChildAt(i);
- captureTransitioningViews(transitioningViews, child);
- }
- }
- } else {
- transitioningViews.add(view);
- }
- }
- }
-
- /**
- * Finds all views that have transition names in the hierarchy under the given view and
- * stores them in {@code namedViews} map with the name as the key.
- */
- void findNamedViews(Map<String, View> namedViews, @NonNull View view) {
- if (view.getVisibility() == View.VISIBLE) {
- String transitionName = ViewCompat.getTransitionName(view);
- if (transitionName != null) {
- namedViews.put(transitionName, view);
- }
- if (view instanceof ViewGroup) {
- ViewGroup viewGroup = (ViewGroup) view;
- int count = viewGroup.getChildCount();
- for (int i = 0; i < count; i++) {
- View child = viewGroup.getChildAt(i);
- findNamedViews(namedViews, child);
- }
- }
- }
- }
-
- /**
- *Applies the prepared {@code nameOverrides} to the view hierarchy.
- */
- void setNameOverridesOrdered(final View sceneRoot,
- final ArrayList<View> sharedElementsIn, final Map<String, String> nameOverrides) {
- OneShotPreDrawListener.add(sceneRoot, new Runnable() {
- @Override
- public void run() {
- final int numSharedElements = sharedElementsIn.size();
- for (int i = 0; i < numSharedElements; i++) {
- View view = sharedElementsIn.get(i);
- String name = ViewCompat.getTransitionName(view);
- if (name != null) {
- String inName = findKeyForValue(nameOverrides, name);
- ViewCompat.setTransitionName(view, inName);
- }
- }
- }
- });
- }
-
- /**
* After the transition has started, remove all targets that we added to the transitions
* so that the transitions are left in a clean state.
*/
@@ -334,22 +264,6 @@
*/
public abstract void setEpicenter(Object transitionObj, Rect epicenter);
- void scheduleNameReset(final ViewGroup sceneRoot,
- final ArrayList<View> sharedElementsIn, final Map<String, String> nameOverrides) {
- OneShotPreDrawListener.add(sceneRoot, new Runnable() {
- @Override
- public void run() {
- final int numSharedElements = sharedElementsIn.size();
- for (int i = 0; i < numSharedElements; i++) {
- final View view = sharedElementsIn.get(i);
- final String name = ViewCompat.getTransitionName(view);
- final String inName = nameOverrides.get(name);
- ViewCompat.setTransitionName(view, inName);
- }
- }
- });
- }
-
/**
* Uses a breadth-first scheme to add startView and all of its children to views.
* It won't add a child if it is already in views.
@@ -395,17 +309,4 @@
return list == null || list.isEmpty();
}
- /**
- * Utility to find the String key in {@code map} that maps to {@code value}.
- */
- @SuppressWarnings("WeakerAccess")
- static String findKeyForValue(Map<String, String> map, String value) {
- for (Map.Entry<String, String> entry : map.entrySet()) {
- if (value.equals(entry.getValue())) {
- return entry.getKey();
- }
- }
- return null;
- }
-
}
diff --git a/navigation/navigation-common/src/androidTest/java/androidx/navigation/NavTypeTest.kt b/navigation/navigation-common/src/androidTest/java/androidx/navigation/NavTypeTest.kt
index f01b81f..4659a2b 100644
--- a/navigation/navigation-common/src/androidTest/java/androidx/navigation/NavTypeTest.kt
+++ b/navigation/navigation-common/src/androidTest/java/androidx/navigation/NavTypeTest.kt
@@ -48,6 +48,8 @@
private val parcelable = ActivityInfo()
private val parcelables = arrayOf(parcelable)
private val en = Bitmap.Config.ALPHA_8
+ private val enString = "ALPHA_8"
+ private val enStringCasing = "alpha_8"
private val serializable = Person()
private val serializables = arrayOf(Bitmap.Config.ALPHA_8)
private val parcelableNavType = NavType.ParcelableType(ActivityInfo::class.java)
@@ -242,4 +244,13 @@
assertThat(NavType.ReferenceType.parseValue(referenceHex))
.isEqualTo(reference)
}
+
+ @Test
+ fun parseEnumValue() {
+ assertThat(enumNavType.parseValue(enString))
+ .isEqualTo(en)
+
+ assertThat(enumNavType.parseValue(enStringCasing))
+ .isEqualTo(en)
+ }
}
diff --git a/navigation/navigation-common/src/main/java/androidx/navigation/NavType.kt b/navigation/navigation-common/src/main/java/androidx/navigation/NavType.kt
index 7b11712..ef64348 100644
--- a/navigation/navigation-common/src/main/java/androidx/navigation/NavType.kt
+++ b/navigation/navigation-common/src/main/java/androidx/navigation/NavType.kt
@@ -749,12 +749,9 @@
*/
@Suppress("RECEIVER_NULLABILITY_MISMATCH_BASED_ON_JAVA_ANNOTATIONS")
public override fun parseValue(value: String): D {
- for (constant in type.enumConstants) {
- if ((constant as Enum<*>).name == value) {
- return constant
- }
- }
- throw IllegalArgumentException(
+ return type.enumConstants.firstOrNull { constant ->
+ constant.name.equals(value, ignoreCase = true)
+ } ?: throw IllegalArgumentException(
"Enum value $value not found for type ${type.name}."
)
}
diff --git a/samples/Support4Demos/src/main/java/com/example/android/supportv4/widget/SlidingPaneLayoutActivity.java b/samples/Support4Demos/src/main/java/com/example/android/supportv4/widget/SlidingPaneLayoutActivity.java
index 0e03ec8..c330ff7 100644
--- a/samples/Support4Demos/src/main/java/com/example/android/supportv4/widget/SlidingPaneLayoutActivity.java
+++ b/samples/Support4Demos/src/main/java/com/example/android/supportv4/widget/SlidingPaneLayoutActivity.java
@@ -18,8 +18,11 @@
package com.example.android.supportv4.widget;
import android.annotation.SuppressLint;
+import android.app.ActionBar;
import android.os.Build;
import android.os.Bundle;
+import android.view.Menu;
+import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewTreeObserver;
@@ -98,15 +101,27 @@
}
@Override
+ public boolean onCreateOptionsMenu(@NonNull Menu menu) {
+ MenuInflater inflater = getMenuInflater();
+ inflater.inflate(R.menu.sliding_pane_layout_menu, menu);
+ return true;
+ }
+
+ @Override
public boolean onOptionsItemSelected(@NonNull MenuItem item) {
- /*
- * The action bar up action should close the detail view if it is
- * currently open, as the left pane contains content one level up in the navigation
- * hierarchy.
- */
- if (item.getItemId() == android.R.id.home && mSlidingLayout.isOpen()) {
- mSlidingLayout.closePane();
- return true;
+ switch (item.getItemId()) {
+ case R.id.lock_mode_unlocked:
+ mSlidingLayout.setLockMode(SlidingPaneLayout.LOCK_MODE_UNLOCKED);
+ return true;
+ case R.id.lock_mode_locked_open:
+ mSlidingLayout.setLockMode(SlidingPaneLayout.LOCK_MODE_LOCKED_OPEN);
+ return true;
+ case R.id.lock_mode_locked_closed:
+ mSlidingLayout.setLockMode(SlidingPaneLayout.LOCK_MODE_LOCKED_CLOSED);
+ return true;
+ case R.id.lock_mode_locked:
+ mSlidingLayout.setLockMode(SlidingPaneLayout.LOCK_MODE_LOCKED);
+ return true;
}
return super.onOptionsItemSelected(item);
}
@@ -204,12 +219,40 @@
/**
* Stub action bar helper; this does nothing.
*/
- static class ActionBarHelper {
- public void init() {}
- public void onPanelClosed() {}
- public void onPanelOpened() {}
- public void onFirstLayout() {}
- public void setTitle(CharSequence title) {}
+ private class ActionBarHelper {
+ private final ActionBar mActionBar;
+ private CharSequence mListTitle;
+ private CharSequence mDetailTitle;
+
+ ActionBarHelper() {
+ mActionBar = getActionBar();
+ }
+
+ public void init() {
+ mListTitle = mDetailTitle = getTitle();
+ }
+
+ public void onPanelClosed() {
+ mActionBar.setTitle(mListTitle);
+ }
+
+ public void onPanelOpened() {
+ mActionBar.setTitle(mDetailTitle);
+ }
+
+ public void onFirstLayout() {
+ if (mSlidingLayout.isSlideable() && !mSlidingLayout.isOpen()) {
+ onPanelClosed();
+ } else {
+ onPanelOpened();
+ }
+ }
+
+ public void setTitle(CharSequence title) {
+ mListTitle = title;
+ }
}
+
+
}
diff --git a/samples/Support4Demos/src/main/res/menu/sliding_pane_layout_menu.xml b/samples/Support4Demos/src/main/res/menu/sliding_pane_layout_menu.xml
new file mode 100644
index 0000000..a967f08
--- /dev/null
+++ b/samples/Support4Demos/src/main/res/menu/sliding_pane_layout_menu.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?><!--
+ Copyright 2021 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ -->
+
+<menu xmlns:android="http://schemas.android.com/apk/res/android">
+ <item android:id="@+id/lock_mode_unlocked"
+ android:title="UNLOCK" />
+ <item android:id="@+id/lock_mode_locked_open"
+ android:title="LOCKED_OPEN" />
+ <item android:id="@+id/lock_mode_locked_closed"
+ android:title="LOCKED_CLOSED" />
+ <item android:id="@+id/lock_mode_locked"
+ android:title="LOCKED" />
+</menu>
\ No newline at end of file
diff --git a/slidingpanelayout/slidingpanelayout/src/androidTest/java/androidx/slidingpanelayout/widget/LockModeTest.kt b/slidingpanelayout/slidingpanelayout/src/androidTest/java/androidx/slidingpanelayout/widget/LockModeTest.kt
index 6b5ab24..faf82fa 100644
--- a/slidingpanelayout/slidingpanelayout/src/androidTest/java/androidx/slidingpanelayout/widget/LockModeTest.kt
+++ b/slidingpanelayout/slidingpanelayout/src/androidTest/java/androidx/slidingpanelayout/widget/LockModeTest.kt
@@ -22,11 +22,12 @@
import androidx.slidingpanelayout.widget.SlidingPaneLayout.LOCK_MODE_LOCKED
import androidx.slidingpanelayout.widget.SlidingPaneLayout.LOCK_MODE_UNLOCKED
import androidx.slidingpanelayout.widget.helpers.TestActivity
+import androidx.slidingpanelayout.widget.helpers.addWaitForCloseLatch
import androidx.slidingpanelayout.widget.helpers.addWaitForOpenLatch
-import androidx.slidingpanelayout.widget.helpers.dragLeft
-import androidx.slidingpanelayout.widget.helpers.dragRight
-import androidx.slidingpanelayout.widget.helpers.findViewX
+import androidx.slidingpanelayout.widget.helpers.addWaitForSlideLatch
import androidx.slidingpanelayout.widget.helpers.openPane
+import androidx.slidingpanelayout.widget.helpers.slideClose
+import androidx.slidingpanelayout.widget.helpers.slideOpen
import androidx.test.core.app.ActivityScenario
import androidx.test.espresso.Espresso.onView
import androidx.test.espresso.assertion.ViewAssertions
@@ -36,6 +37,7 @@
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.LargeTest
import androidx.test.filters.SdkSuppress
+import androidx.testutils.withActivity
import com.google.common.truth.Truth.assertThat
import org.junit.After
import org.junit.Test
@@ -64,11 +66,15 @@
ViewMatchers.withEffectiveVisibility(ViewMatchers.Visibility.VISIBLE)
)
)
+ val slidingPaneLayout =
+ withActivity { findViewById<SlidingPaneLayout>(R.id.sliding_pane_layout) }
+ assertThat(slidingPaneLayout.isOpen).isFalse()
+ assertThat(slidingPaneLayout.isSlideable).isTrue()
}
}
/**
- * Test users can freely swipe right between list and detail panes when lock mode set to
+ * Test users can swipe right between list and detail panes when lock mode set to
* LOCK_MODE_UNLOCKED.
*/
@SdkSuppress(maxSdkVersion = 28) // TODO: Fix flaky test issues on API 30 Cuttlefish devices.
@@ -81,18 +87,19 @@
}
with(ActivityScenario.launch(TestActivity::class.java)) {
- val latch = addWaitForOpenLatch(R.id.sliding_pane_layout)
+ val panelOpenCountDownLatch = addWaitForOpenLatch(R.id.sliding_pane_layout)
onView(withId(R.id.sliding_pane_layout)).perform(openPane())
- assertThat(latch.await(2, TimeUnit.SECONDS)).isTrue()
- latch.await(2, TimeUnit.SECONDS)
- val detailPaneOpenX = findViewX(R.id.detail_pane)
- onView(withId(R.id.sliding_pane_layout)).perform(dragRight())
- assertThat(findViewX(R.id.detail_pane)).isGreaterThan(detailPaneOpenX)
+ // wait for detail pane open
+ assertThat(panelOpenCountDownLatch.await(2, TimeUnit.SECONDS)).isTrue()
+ val panelSlideCountDownLatch = addWaitForSlideLatch(R.id.sliding_pane_layout)
+ onView(withId(R.id.sliding_pane_layout)).perform(slideClose())
+ // wait for detail pane sliding
+ assertThat(panelSlideCountDownLatch.await(2, TimeUnit.SECONDS)).isTrue()
}
}
/**
- * Test users can freely swipe left between list and detail panes when lock mode set to
+ * Test users can swipe left between list and detail panes when lock mode set to
* LOCK_MODE_UNLOCKED.
*/
@SdkSuppress(maxSdkVersion = 28) // TODO: Fix flaky test issues on API 30 Cuttlefish devices.
@@ -105,19 +112,20 @@
}
with(ActivityScenario.launch(TestActivity::class.java)) {
- val detailPaneOpenX = findViewX(R.id.detail_pane)
- onView(withId(R.id.sliding_pane_layout)).perform(dragLeft())
- assertThat(findViewX(R.id.detail_pane)).isLessThan(detailPaneOpenX)
+ val panelSlideCountDownLatch = addWaitForSlideLatch(R.id.sliding_pane_layout)
+ onView(withId(R.id.sliding_pane_layout)).perform(slideOpen())
+ // wait for detail pane sliding
+ assertThat(panelSlideCountDownLatch.await(2, TimeUnit.SECONDS)).isTrue()
}
}
/**
- * Test users cannot swipe from list to detail, but can swipe from detail to list when lock
- * mode set to LOCK_MODE_LOCKED_OPEN
+ * Test users can swipe to open detail pane in lock mode LOCK_MODE_LOCKED_OPEN when
+ * detail view is in closed state. Otherwise, users cannot swipe it.
*/
@SdkSuppress(maxSdkVersion = 28) // TODO: Fix flaky test issues on API 30 Cuttlefish devices.
@Test
- public fun testCanSlideListToDetailWhenLockModeLockedOpen() {
+ public fun testSwipeWhenLockModeLockedOpen() {
TestActivity.onActivityCreated = { activity ->
val slidingPaneLayout =
activity.findViewById<SlidingPaneLayout>(R.id.sliding_pane_layout)
@@ -125,21 +133,24 @@
}
with(ActivityScenario.launch(TestActivity::class.java)) {
- val detailPaneClosedX = findViewX(R.id.detail_pane)
- onView(withId(R.id.sliding_pane_layout)).perform(dragRight())
- assertThat(findViewX(R.id.detail_pane)).isEqualTo(detailPaneClosedX)
- onView(withId(R.id.sliding_pane_layout)).perform(dragLeft())
- assertThat(findViewX(R.id.detail_pane)).isLessThan(detailPaneClosedX)
+ var panelSlideCountDownLatch = addWaitForSlideLatch(R.id.sliding_pane_layout)
+ onView(withId(R.id.sliding_pane_layout)).perform(slideOpen())
+ // can slide to open
+ assertThat(panelSlideCountDownLatch.await(2, TimeUnit.SECONDS)).isTrue()
+ panelSlideCountDownLatch = addWaitForCloseLatch(R.id.sliding_pane_layout)
+ onView(withId(R.id.sliding_pane_layout)).perform(slideClose())
+ // cannot slide to close
+ assertThat(panelSlideCountDownLatch.await(2, TimeUnit.SECONDS)).isFalse()
}
}
/**
- * Test users cannot swipe from detail to list, but can swipe from list to detail when lock
- * mode set to LOCK_MODE_LOCKED_CLOSED
+ * Test users can swipe to close the detail pane in lock mode LOCK_MODE_LOCKED_CLOSED when
+ * detail view is in open state. Otherwise, users cannot swipe it.
*/
@SdkSuppress(maxSdkVersion = 28) // TODO: Fix flaky test issues on API 30 Cuttlefish devices.
@Test
- public fun testSwipeWhenLockModeClosed() {
+ public fun testSwipeWhenLockModeLockedClosed() {
TestActivity.onActivityCreated = { activity ->
val slidingPaneLayout =
activity.findViewById<SlidingPaneLayout>(R.id.sliding_pane_layout)
@@ -147,15 +158,17 @@
}
with(ActivityScenario.launch(TestActivity::class.java)) {
- var detailPaneClosedX = findViewX(R.id.detail_pane)
- onView(withId(R.id.sliding_pane_layout)).perform(dragLeft())
- assertThat(findViewX(R.id.detail_pane)).isEqualTo(detailPaneClosedX)
+ var panelSlideCountDownLatch = addWaitForSlideLatch(R.id.sliding_pane_layout)
+ onView(withId(R.id.sliding_pane_layout)).perform(slideOpen())
+ // cannot slide to open
+ assertThat(panelSlideCountDownLatch.await(2, TimeUnit.SECONDS)).isFalse()
val latch = addWaitForOpenLatch(R.id.sliding_pane_layout)
onView(withId(R.id.sliding_pane_layout)).perform(openPane())
assertThat(latch.await(2, TimeUnit.SECONDS)).isTrue()
- detailPaneClosedX = findViewX(R.id.detail_pane)
- onView(withId(R.id.sliding_pane_layout)).perform(dragRight())
- assertThat(findViewX(R.id.detail_pane)).isGreaterThan(detailPaneClosedX)
+ panelSlideCountDownLatch = addWaitForSlideLatch(R.id.sliding_pane_layout)
+ onView(withId(R.id.sliding_pane_layout)).perform(slideClose())
+ // can slide to close
+ assertThat(panelSlideCountDownLatch.await(2, TimeUnit.SECONDS)).isTrue()
}
}
@@ -173,15 +186,17 @@
}
with(ActivityScenario.launch(TestActivity::class.java)) {
- val detailPaneClosedX = findViewX(R.id.detail_pane)
- onView(withId(R.id.sliding_pane_layout)).perform(dragLeft())
- assertThat(findViewX(R.id.detail_pane)).isEqualTo(detailPaneClosedX)
+ var panelSlideCountDownLatch = addWaitForSlideLatch(R.id.sliding_pane_layout)
+ onView(withId(R.id.sliding_pane_layout)).perform(slideOpen())
+ // cannot slide to open
+ assertThat(panelSlideCountDownLatch.await(2, TimeUnit.SECONDS)).isFalse()
val latch = addWaitForOpenLatch(R.id.sliding_pane_layout)
onView(withId(R.id.sliding_pane_layout)).perform(openPane())
assertThat(latch.await(2, TimeUnit.SECONDS)).isTrue()
- val detailPaneOpenX = findViewX(R.id.detail_pane)
- onView(withId(R.id.sliding_pane_layout)).perform(dragRight())
- assertThat(findViewX(R.id.detail_pane)).isEqualTo(detailPaneOpenX)
+ panelSlideCountDownLatch = addWaitForSlideLatch(R.id.sliding_pane_layout)
+ onView(withId(R.id.sliding_pane_layout)).perform(slideClose())
+ // cannot slide to close
+ assertThat(panelSlideCountDownLatch.await(2, TimeUnit.SECONDS)).isFalse()
}
}
}
\ No newline at end of file
diff --git a/slidingpanelayout/slidingpanelayout/src/androidTest/java/androidx/slidingpanelayout/widget/helpers/ActivityScenario.kt b/slidingpanelayout/slidingpanelayout/src/androidTest/java/androidx/slidingpanelayout/widget/helpers/ActivityScenario.kt
index 2d5a251..4d54fe3 100644
--- a/slidingpanelayout/slidingpanelayout/src/androidTest/java/androidx/slidingpanelayout/widget/helpers/ActivityScenario.kt
+++ b/slidingpanelayout/slidingpanelayout/src/androidTest/java/androidx/slidingpanelayout/widget/helpers/ActivityScenario.kt
@@ -51,3 +51,39 @@
}
return latch
}
+
+public inline fun <reified A : Activity> ActivityScenario<A>.addWaitForCloseLatch(
+ @IdRes resId: Int
+): CountDownLatch {
+ val latch = CountDownLatch(1)
+ withActivity {
+ val slidingPaneLayout = findViewById<SlidingPaneLayout>(resId)
+ slidingPaneLayout.addPanelSlideListener(object : SlidingPaneLayout.PanelSlideListener {
+ override fun onPanelSlide(panel: View, slideOffset: Float) {}
+ override fun onPanelOpened(panel: View) {}
+ override fun onPanelClosed(panel: View) {
+ latch.countDown()
+ slidingPaneLayout.removePanelSlideListener(this)
+ }
+ })
+ }
+ return latch
+}
+
+public inline fun <reified A : Activity> ActivityScenario<A>.addWaitForSlideLatch(
+ @IdRes resId: Int
+): CountDownLatch {
+ val latch = CountDownLatch(1)
+ withActivity {
+ val slidingPaneLayout = findViewById<SlidingPaneLayout>(resId)
+ slidingPaneLayout.addPanelSlideListener(object : SlidingPaneLayout.PanelSlideListener {
+ override fun onPanelSlide(panel: View, slideOffset: Float) {
+ latch.countDown()
+ slidingPaneLayout.removePanelSlideListener(this)
+ }
+ override fun onPanelOpened(panel: View) {}
+ override fun onPanelClosed(panel: View) {}
+ })
+ }
+ return latch
+}
\ No newline at end of file
diff --git a/slidingpanelayout/slidingpanelayout/src/androidTest/java/androidx/slidingpanelayout/widget/helpers/Espresso.kt b/slidingpanelayout/slidingpanelayout/src/androidTest/java/androidx/slidingpanelayout/widget/helpers/Espresso.kt
index 80b7429..b55f81e 100644
--- a/slidingpanelayout/slidingpanelayout/src/androidTest/java/androidx/slidingpanelayout/widget/helpers/Espresso.kt
+++ b/slidingpanelayout/slidingpanelayout/src/androidTest/java/androidx/slidingpanelayout/widget/helpers/Espresso.kt
@@ -28,10 +28,10 @@
import androidx.test.espresso.matcher.ViewMatchers
import org.hamcrest.Matcher
-public fun dragRight(): ViewAction? {
+public fun slideClose(): ViewAction? {
return ViewActions.actionWithAssertions(
GeneralSwipeAction(
- Swipe.SLOW,
+ Swipe.FAST,
GeneralLocation.CENTER_LEFT,
GeneralLocation.CENTER_RIGHT,
Press.FINGER
@@ -39,10 +39,10 @@
)
}
-public fun dragLeft(): ViewAction? {
+public fun slideOpen(): ViewAction? {
return ViewActions.actionWithAssertions(
GeneralSwipeAction(
- Swipe.SLOW,
+ Swipe.FAST,
GeneralLocation.CENTER_RIGHT,
GeneralLocation.CENTER_LEFT,
Press.FINGER
diff --git a/slidingpanelayout/slidingpanelayout/src/main/java/androidx/slidingpanelayout/widget/SlidingPaneLayout.java b/slidingpanelayout/slidingpanelayout/src/main/java/androidx/slidingpanelayout/widget/SlidingPaneLayout.java
index 3138c10..024d6964 100644
--- a/slidingpanelayout/slidingpanelayout/src/main/java/androidx/slidingpanelayout/widget/SlidingPaneLayout.java
+++ b/slidingpanelayout/slidingpanelayout/src/main/java/androidx/slidingpanelayout/widget/SlidingPaneLayout.java
@@ -233,6 +233,8 @@
/**
* Set the lock mode that controls how the user can swipe between the panes.
+ *
+ * @param lockMode The new lock mode for the detail pane.
*/
public final void setLockMode(@LockMode int lockMode) {
mLockMode = lockMode;
@@ -1013,6 +1015,9 @@
}
private boolean closePane(int initialVelocity) {
+ if (!mCanSlide) {
+ mPreservedOpenState = false;
+ }
if (mFirstLayout || smoothSlideTo(1.f, initialVelocity)) {
mPreservedOpenState = false;
return true;
@@ -1021,6 +1026,9 @@
}
private boolean openPane(int initialVelocity) {
+ if (!mCanSlide) {
+ mPreservedOpenState = true;
+ }
if (mFirstLayout || smoothSlideTo(0.f, initialVelocity)) {
mPreservedOpenState = true;
return true;
@@ -1475,6 +1483,7 @@
SavedState ss = new SavedState(superState);
ss.isOpen = isSlideable() ? isOpen() : mPreservedOpenState;
+ ss.mLockMode = mLockMode;
return ss;
}
@@ -1495,6 +1504,8 @@
closePane();
}
mPreservedOpenState = ss.isOpen;
+
+ setLockMode(ss.mLockMode);
}
private class DragHelperCallback extends ViewDragHelper.Callback {
@@ -1504,7 +1515,7 @@
@Override
public boolean tryCaptureView(View child, int pointerId) {
- if (mIsUnableToDrag) {
+ if (!isDraggable()) {
return false;
}
@@ -1566,20 +1577,7 @@
@Override
public int clampViewPositionHorizontal(View child, int left, int dx) {
- boolean slidingDetailToList = isLayoutRtlSupport() ? dx > 0 : dx < 0;
int newLeft = left;
- if (slidingDetailToList) {
- if (getLockMode() == LOCK_MODE_LOCKED_CLOSED
- || getLockMode() == LOCK_MODE_LOCKED) {
- newLeft -= dx;
- }
- } else {
- if (getLockMode() == LOCK_MODE_LOCKED_OPEN
- || getLockMode() == LOCK_MODE_LOCKED) {
- newLeft -= dx;
- }
- }
-
final LayoutParams lp = (LayoutParams) mSlideableView.getLayoutParams();
if (isLayoutRtlSupport()) {
@@ -1604,15 +1602,39 @@
@Override
public void onEdgeTouched(int edgeFlags, int pointerId) {
+ if (!isDraggable()) {
+ return;
+ }
mDragHelper.captureChildView(mSlideableView, pointerId);
}
@Override
public void onEdgeDragStarted(int edgeFlags, int pointerId) {
+ if (!isDraggable()) {
+ return;
+ }
mDragHelper.captureChildView(mSlideableView, pointerId);
}
+
+ private boolean isDraggable() {
+ if (mIsUnableToDrag) {
+ return false;
+ }
+ if (getLockMode() == LOCK_MODE_LOCKED) {
+ return false;
+ }
+ if (isOpen() && getLockMode() == LOCK_MODE_LOCKED_OPEN) {
+ return false;
+ }
+ if (!isOpen() && getLockMode() == LOCK_MODE_LOCKED_CLOSED) {
+ return false;
+ }
+ return true;
+ }
}
+
+
public static class LayoutParams extends ViewGroup.MarginLayoutParams {
private static final int[] ATTRS = new int[]{
android.R.attr.layout_weight
@@ -1670,6 +1692,7 @@
static class SavedState extends AbsSavedState {
boolean isOpen;
+ @LockMode int mLockMode;
SavedState(Parcelable superState) {
super(superState);
@@ -1678,12 +1701,14 @@
SavedState(Parcel in, ClassLoader loader) {
super(in, loader);
isOpen = in.readInt() != 0;
+ mLockMode = in.readInt();
}
@Override
public void writeToParcel(Parcel out, int flags) {
super.writeToParcel(out, flags);
out.writeInt(isOpen ? 1 : 0);
+ out.writeInt(mLockMode);
}
public static final Creator<SavedState> CREATOR = new ClassLoaderCreator<SavedState>() {
diff --git a/transition/transition/src/androidTest/java/androidx/transition/FragmentTestUtil.kt b/transition/transition/src/androidTest/java/androidx/transition/FragmentTestUtil.kt
index 7f5a337..1579cd7b 100644
--- a/transition/transition/src/androidTest/java/androidx/transition/FragmentTestUtil.kt
+++ b/transition/transition/src/androidTest/java/androidx/transition/FragmentTestUtil.kt
@@ -25,9 +25,9 @@
import androidx.fragment.app.FragmentManager
import androidx.fragment.app.FragmentTransaction
import androidx.fragment.app.TargetTracking
-import androidx.transition.test.R
import androidx.test.platform.app.InstrumentationRegistry
import androidx.testutils.runOnUiThreadRethrow
+import androidx.transition.test.R
import com.google.common.truth.Truth.assertThat
import com.google.common.truth.Truth.assertWithMessage
import java.lang.ref.WeakReference
@@ -43,27 +43,6 @@
object Reordered : ReorderingAllowed()
object Ordered : ReorderingAllowed()
-sealed class StateManager {
- abstract fun setup()
-
- override fun toString(): String = this.javaClass.simpleName
-
- fun teardown() {
- // Reset it back to the default
- FragmentManager.enableNewStateManager(true)
- }
-}
-object NewStateManager : StateManager() {
- override fun setup() {
- FragmentManager.enableNewStateManager(true)
- }
-}
-object OldStateManager : StateManager() {
- override fun setup() {
- FragmentManager.enableNewStateManager(false)
- }
-}
-
@Suppress("DEPRECATION")
fun androidx.test.rule.ActivityTestRule<out FragmentActivity>.executePendingTransactions(
fm: FragmentManager = activity.supportFragmentManager
diff --git a/transition/transition/src/androidTest/java/androidx/transition/FragmentTransitionTest.kt b/transition/transition/src/androidTest/java/androidx/transition/FragmentTransitionTest.kt
index c45abaf..db5d385 100644
--- a/transition/transition/src/androidTest/java/androidx/transition/FragmentTransitionTest.kt
+++ b/transition/transition/src/androidTest/java/androidx/transition/FragmentTransitionTest.kt
@@ -15,7 +15,6 @@
*/
package androidx.transition
-import android.graphics.Rect
import android.os.Build
import android.os.Bundle
import android.view.View
@@ -50,8 +49,7 @@
@RunWith(Parameterized::class)
@SdkSuppress(minSdkVersion = Build.VERSION_CODES.LOLLIPOP)
class FragmentTransitionTest(
- private val reorderingAllowed: ReorderingAllowed,
- private val stateManager: StateManager
+ private val reorderingAllowed: ReorderingAllowed
) {
@Suppress("DEPRECATION")
@@ -68,7 +66,6 @@
@Before
fun setup() {
- stateManager.setup()
onBackStackChangedTimes = 0
fragmentManager = activityRule.activity.supportFragmentManager
fragmentManager.addOnBackStackChangedListener(onBackStackChangedListener)
@@ -77,7 +74,6 @@
@After
fun teardown() {
fragmentManager.removeOnBackStackChangedListener(onBackStackChangedListener)
- stateManager.teardown()
}
// Test that normal view transitions (enter, exit, reenter, return) run with
@@ -130,8 +126,6 @@
fun removeThenAddBeforeTransitionFinishes() {
// enter transition
val fragment = setupInitialFragment()
- val blue = activityRule.findBlue()
- val green = activityRule.findGreen()
val view1 = fragment.view
@@ -155,23 +149,10 @@
// back stack
if (reorderingAllowed is Reordered) {
assertThat(onBackStackChangedTimes).isEqualTo(2)
- assertThat(fragment.requireView()).isEqualTo(view1)
} else {
assertThat(onBackStackChangedTimes).isEqualTo(3)
- if (stateManager is NewStateManager) {
- // When using FragmentStateManager, the transition gets cancelled and the
- // Fragment does not go all the way through to destroying the view before
- // coming back up, so the view instances will still match
- assertThat(fragment.requireView()).isEqualTo(view1)
- } else {
- // If reorder is not allowed we will get the exit Transition
- fragment.waitForTransition()
- fragment.exitTransition.verifyAndClearTransition {
- exitingViews += listOf(green, blue)
- }
- assertThat(fragment.requireView()).isNotEqualTo(view1)
- }
}
+ assertThat(fragment.requireView()).isEqualTo(view1)
verifyNoOtherTransitions(fragment)
}
@Test
@@ -860,7 +841,6 @@
val startBlue = activityRule.findBlue()
val startGreen = activityRule.findGreen()
- val startBlueBounds = startBlue.boundsOnScreen
fragmentManager.beginTransaction()
.addSharedElement(startBlue, "fooSquare")
@@ -876,21 +856,8 @@
val endBlue = activityRule.findBlue()
val endGreen = activityRule.findGreen()
- // FragmentStateManager is able to build the correct transition
- // whether you use reordering or not
- if (stateManager is NewStateManager || reorderingAllowed is Reordered) {
- fragment1.exitTransition.verifyAndClearTransition {
- exitingViews += listOf(startGreen, startBlue)
- }
- } else {
- fragment1.exitTransition.verifyAndClearTransition {
- epicenter = startBlueBounds
- exitingViews += startGreen
- }
- fragment2.sharedElementEnter.verifyAndClearTransition {
- epicenter = startBlueBounds
- exitingViews += startBlue
- }
+ fragment1.exitTransition.verifyAndClearTransition {
+ exitingViews += listOf(startGreen, startBlue)
}
verifyNoOtherTransitions(fragment1)
@@ -1030,16 +997,10 @@
if (reorderingAllowed is Reordered) {
// reordering allowed fragment3 to get a transition so we should wait for it to finish
fragment3.waitForTransition()
- if (stateManager is NewStateManager) {
- // When using the NewStateManager, the last operation sets the direction.
- // In this case, the forward direction since we did a replace() after the pop
- fragment2.exitTransition.verifyAndClearTransition {
- exitingViews += listOf(midGreen, midBlue)
- }
- } else {
- fragment2.returnTransition.verifyAndClearTransition {
- exitingViews += listOf(midGreen, midBlue)
- }
+ // The last operation (in this case a replace()) sets the direction of
+ // the transition, so the popped fragment runs its exit transition
+ fragment2.exitTransition.verifyAndClearTransition {
+ exitingViews += listOf(midGreen, midBlue)
}
val endGreen = activityRule.findGreen()
val endBlue = activityRule.findBlue()
@@ -1094,30 +1055,18 @@
val endGreen = activityRule.findGreen()
val endBlue = activityRule.findBlue()
val endGreenBounds = endGreen.boundsOnScreen
-
- if (stateManager is NewStateManager) {
- // When using the NewStateManager, the last operation sets the direction.
- // In this case, the forward direction since we did a replace() after the pop
- fragment1.exitTransition.verifyAndClearTransition {
- epicenter = endGreenBounds
- exitingViews += startGreen
- }
- } else {
- fragment1.returnTransition.verifyAndClearTransition {
- exitingViews += startGreen
- }
+ // The last operation (in this case a replace()) sets the direction of
+ // the transition, so the popped fragment runs its exit transition
+ fragment1.exitTransition.verifyAndClearTransition {
+ epicenter = endGreenBounds
+ exitingViews += startGreen
}
fragment2.enterTransition.verifyAndClearTransition {
epicenter = startGreenBounds
enteringViews += endGreen
}
fragment2.sharedElementEnter.verifyAndClearTransition {
- epicenter = if (stateManager is NewStateManager) {
- endGreenBounds
- } else {
- // In this case, we can't find an epicenter
- Rect()
- }
+ epicenter = endGreenBounds
exitingViews += startBlue
enteringViews += endBlue
}
@@ -1156,20 +1105,10 @@
val midRed = activityRule.findRed()
val midGreenBounds = midGreen.boundsOnScreen
- // FragmentStateManager is able to build the correct transition
- // whether you use reordering or not
- if (stateManager is NewStateManager || reorderingAllowed is Reordered) {
- fragment2.sharedElementEnter.verifyAndClearTransition {
- epicenter = startGreenBounds
- exitingViews += startGreen
- enteringViews += midGreen
- }
- } else {
- fragment2.sharedElementEnter.verifyAndClearTransition {
- epicenter = startGreenBounds
- exitingViews += listOf(startGreen, startBlue)
- enteringViews += midGreen
- }
+ fragment2.sharedElementEnter.verifyAndClearTransition {
+ epicenter = startGreenBounds
+ exitingViews += startGreen
+ enteringViews += midGreen
}
fragment2.enterTransition.verifyAndClearTransition {
epicenter = midGreenBounds
@@ -1535,18 +1474,8 @@
companion object {
@JvmStatic
- @Parameterized.Parameters(name = "ordering={0}, stateManager={1}")
- fun data() = mutableListOf<Array<Any>>().apply {
- arrayOf(
- Ordered,
- Reordered
- ).forEach { ordering ->
- // Run the test with the new state manager
- add(arrayOf(ordering, NewStateManager))
- // Run the test with the old state manager
- add(arrayOf(ordering, OldStateManager))
- }
- }
+ @Parameterized.Parameters(name = "ordering={0}")
+ fun data() = arrayOf(Ordered, Reordered)
}
}