diff options
author | android-build-prod (mdb) <android-build-team-robot@google.com> | 2019-11-22 12:51:49 +0000 |
---|---|---|
committer | android-build-prod (mdb) <android-build-team-robot@google.com> | 2019-11-22 12:51:49 +0000 |
commit | fc517f62dbd3175989aba068cd58445db4cbb391 (patch) | |
tree | a9ee7b72baf3a48be9af7dccd2b68ff83b61ae91 | |
parent | 162f8b1fdf3b5ab651ae8ef2b44f3ab79b008062 (diff) | |
parent | 6f8e163bd0cc48e08f3b4f42347719d58c3a780f (diff) | |
download | support-fc517f62dbd3175989aba068cd58445db4cbb391.tar.gz |
Snap for 6026338 from 6f8e163bd0cc48e08f3b4f42347719d58c3a780f to androidx-browser-release
Change-Id: I2896dd1a076c16b9d31b92abbf7c8a18bbbc7e17
100 files changed, 2331 insertions, 260 deletions
diff --git a/buildSrc/src/main/kotlin/androidx/build/AndroidXPlugin.kt b/buildSrc/src/main/kotlin/androidx/build/AndroidXPlugin.kt index ff11c68ae82..b3a054b2f5e 100644 --- a/buildSrc/src/main/kotlin/androidx/build/AndroidXPlugin.kt +++ b/buildSrc/src/main/kotlin/androidx/build/AndroidXPlugin.kt @@ -114,11 +114,6 @@ class AndroidXPlugin : Plugin<Project> { if (project.isRoot) { project.configureRootProject() } - // Compose-compiler can't have the standard library plugins applied to mark it a library - // to trigger e.g. build-info generation. See b/142323149. - if (project.name == "compose-compiler") { - project.addCreateLibraryBuildInfoFileTask(androidXExtension) - } project.configureJacoco() diff --git a/buildSrc/src/main/kotlin/androidx/build/CreateLibraryBuildInfoFileTask.kt b/buildSrc/src/main/kotlin/androidx/build/CreateLibraryBuildInfoFileTask.kt index e6da539d2d7..676de5786c6 100644 --- a/buildSrc/src/main/kotlin/androidx/build/CreateLibraryBuildInfoFileTask.kt +++ b/buildSrc/src/main/kotlin/androidx/build/CreateLibraryBuildInfoFileTask.kt @@ -68,7 +68,10 @@ open class CreateLibraryBuildInfoFileTask : DefaultTask() { return library?.mavenGroup?.requireSameVersion ?: false } - private fun getCommitShaAtHead(): String { + /* For androidx release notes, the most common use case is to track and publish the last sha + * of the build that is released. Thus, we use frameworks/support to get the sha + */ + private fun getFrameworksSupportCommitShaAtHead(): String { val commitList: List<Commit> = GitClientImpl(project.rootDir).getGitLog( GitCommitRange( fromExclusive = "", @@ -76,7 +79,7 @@ open class CreateLibraryBuildInfoFileTask : DefaultTask() { n = 1 ), keepMerges = true, - fullProjectDir = project.projectDir + fullProjectDir = getSupportRoot(project) ) return commitList.first().sha } @@ -108,7 +111,7 @@ open class CreateLibraryBuildInfoFileTask : DefaultTask() { libraryBuildInfoFile.groupId = project.group.toString() libraryBuildInfoFile.version = project.version.toString() libraryBuildInfoFile.path = getProjectSpecificDirectory() - libraryBuildInfoFile.sha = getCommitShaAtHead() + libraryBuildInfoFile.sha = getFrameworksSupportCommitShaAtHead() libraryBuildInfoFile.groupIdRequiresSameVersion = requiresSameVersion() val libraryDependencies = ArrayList<LibraryBuildInfoFile.Dependency>() val checks = ArrayList<LibraryBuildInfoFile.Check>() diff --git a/camera/camera-view/build.gradle b/camera/camera-view/build.gradle index bef2a184263..1d648232bed 100644 --- a/camera/camera-view/build.gradle +++ b/camera/camera-view/build.gradle @@ -40,6 +40,8 @@ dependencies { androidTestImplementation(ANDROIDX_TEST_RULES) androidTestImplementation(TRUTH) androidTestImplementation(project(":camera:camera-camera2")) + androidTestImplementation(MOCKITO_CORE, libs.exclude_bytebuddy) // DexMaker has it's own MockMaker + androidTestImplementation(DEXMAKER_MOCKITO, libs.exclude_bytebuddy) // DexMaker has it's own MockMaker } android { defaultConfig { diff --git a/camera/camera-view/src/androidTest/java/androidx/camera/view/SurfaceTextureReleaseBlockingListenerTest.java b/camera/camera-view/src/androidTest/java/androidx/camera/view/SurfaceTextureReleaseBlockingListenerTest.java index f49fa9eaaf3..2ae3a29797b 100644 --- a/camera/camera-view/src/androidTest/java/androidx/camera/view/SurfaceTextureReleaseBlockingListenerTest.java +++ b/camera/camera-view/src/androidTest/java/androidx/camera/view/SurfaceTextureReleaseBlockingListenerTest.java @@ -20,15 +20,19 @@ import static com.google.common.truth.Truth.assertThat; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; +import static org.mockito.Mockito.atLeastOnce; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; import android.content.Context; import android.graphics.SurfaceTexture; import android.view.TextureView; -import androidx.annotation.RequiresApi; import androidx.concurrent.futures.CallbackToFutureAdapter; import androidx.test.core.app.ApplicationProvider; import androidx.test.ext.junit.runners.AndroidJUnit4; +import androidx.test.filters.SdkSuppress; import androidx.test.filters.SmallTest; import com.google.common.util.concurrent.ListenableFuture; @@ -59,8 +63,12 @@ public class SurfaceTextureReleaseBlockingListenerTest { new SurfaceTextureReleaseBlockingListener(mTextureView); } - // isReleased() exists only on 23 and above. Is private prior to 26 - @RequiresApi(23) + /** + * {@link SurfaceTexture#isReleased()} exists only on 23 and above. Is private prior to 26. + * @see #surfaceIsValid_ifOnlyReleasedByTextureView22below() for equivalent test on API 22 and + * below + */ + @SdkSuppress(minSdkVersion = 23) @Test public void surfaceIsValid_ifOnlyReleasedByTextureView() { SurfaceTexture surfaceTexture = new SurfaceTexture(0); @@ -74,8 +82,30 @@ public class SurfaceTextureReleaseBlockingListenerTest { assertFalse(surfaceTexture.isReleased()); } - // isReleased() exists only on 23 and above. Is private prior to 26 - @RequiresApi(23) + /** + * {@link SurfaceTexture#isReleased()} exists only on 23 and above. Is private prior to 26. + * @see #surfaceIsValid_ifOnlyReleasedByTextureView() for equivalent test on API 23 and above + */ + // See equivalent test for API >= 23 in SurfaceTextureReleaseBlockingListenerAndroidTest + @SdkSuppress(maxSdkVersion = 22) + @Test + public void surfaceIsValid_ifOnlyReleasedByTextureView22below() { + SurfaceTexture surfaceTexture = mock(SurfaceTexture.class); + mSurfaceTextureReleaseBlockingListener.setSurfaceTextureSafely(surfaceTexture, + mSurfaceReleaseFuture); + + // Simulate the TextureView destroying the SurfaceTexture + mSurfaceTextureReleaseBlockingListener.onSurfaceTextureDestroyed(surfaceTexture); + + verify(surfaceTexture, never()).release(); + } + + /** + * {@link SurfaceTexture#isReleased()} exists only on 23 and above. Is private prior to 26. + * @see #surfaceIsValid_ifOnlyReleasedBySurfaceReleaseFuture22below() for equivalent test on API + * 22 and below + */ + @SdkSuppress(minSdkVersion = 23) @Test public void surfaceIsValid_ifOnlyReleasedBySurfaceReleaseFuture() { SurfaceTexture surfaceTexture = new SurfaceTexture(0); @@ -91,8 +121,33 @@ public class SurfaceTextureReleaseBlockingListenerTest { mSurfaceTextureReleaseBlockingListener.onSurfaceTextureDestroyed(surfaceTexture); } - // isReleased() exists only on 23 and above. Is private prior to 26 - @RequiresApi(23) + /** + * {@link SurfaceTexture#isReleased()} exists only on 23 and above. Is private prior to 26. + * @see #surfaceIsValid_ifOnlyReleasedBySurfaceReleaseFuture() for equivalent test on API 23 + * and above + */ + @SdkSuppress(maxSdkVersion = 22) + @Test + public void surfaceIsValid_ifOnlyReleasedBySurfaceReleaseFuture22below() { + SurfaceTexture surfaceTexture = mock(SurfaceTexture.class); + surfaceTexture.detachFromGLContext(); + mSurfaceTextureReleaseBlockingListener.setSurfaceTextureSafely(surfaceTexture, + mSurfaceReleaseFuture); + + mCompleter.set(null); + + verify(surfaceTexture, never()).release(); + + // TODO(b/144878737) Remove when SurfaceTexture null dereference issue fixed + mSurfaceTextureReleaseBlockingListener.onSurfaceTextureDestroyed(surfaceTexture); + } + + /** + * {@link SurfaceTexture#isReleased()} exists only on 23 and above. Is private prior to 26. + * @see #surfaceIsInvalid_ifReleasedByBothTextureViewAndSurfaceReleaseFuture22below() for + * equivalent test on API 22 and below + */ + @SdkSuppress(minSdkVersion = 23) @Test public void surfaceIsInvalid_ifReleasedByBothTextureViewAndSurfaceReleaseFuture() { SurfaceTexture surfaceTexture = new SurfaceTexture(0); @@ -107,6 +162,26 @@ public class SurfaceTextureReleaseBlockingListenerTest { assertTrue(surfaceTexture.isReleased()); } + /** + * {@link SurfaceTexture#isReleased()} exists only on 23 and above. Is private prior to 26. + * @see #surfaceIsInvalid_ifReleasedByBothTextureViewAndSurfaceReleaseFuture() for equivalent + * test on API 23 and above + */ + @SdkSuppress(maxSdkVersion = 22) + @Test + public void surfaceIsInvalid_ifReleasedByBothTextureViewAndSurfaceReleaseFuture22below() { + SurfaceTexture surfaceTexture = mock(SurfaceTexture.class); + surfaceTexture.detachFromGLContext(); + mSurfaceTextureReleaseBlockingListener.setSurfaceTextureSafely(surfaceTexture, + mSurfaceReleaseFuture); + + // Simulate the TextureView destroying the SurfaceTexture + mSurfaceTextureReleaseBlockingListener.onSurfaceTextureDestroyed(surfaceTexture); + mCompleter.set(null); + + verify(surfaceTexture, atLeastOnce()).release(); + } + @Test public void setSurfaceTextureSafely_callsSetSurfaceTextureOnTextureView() { SurfaceTexture surfaceTexture = new SurfaceTexture(0); diff --git a/compose/compose-compiler/build.gradle b/compose/compose-compiler/build.gradle index 5a74087fb6a..d2f1f32f324 100644 --- a/compose/compose-compiler/build.gradle +++ b/compose/compose-compiler/build.gradle @@ -27,37 +27,30 @@ buildscript { } plugins { + id("java") id("AndroidXPlugin") } apply plugin: 'org.anarres.jarjar' -configurations { - jarFiles -} - -// This dependency isn't seen by Jetpad because that artifact declares Publish.NONE -// This dependency doesn't appear in the .pom because the dependency is of type jarFile dependencies { - jarFiles(project(":compose:compose-compiler-hosted")) { - transitive = false - } + compileOnly(project(":compose:compose-compiler-hosted")) } jarjar.repackage('embeddedPlugin') { destinationName "compose-compiler.jar" - from configurations.jarFiles + from configurations.compileClasspath classRename 'com.intellij.**', 'org.jetbrains.kotlin.com.intellij.@1' } configurations { - embeddablePlugin -} - -artifacts { - embeddablePlugin(embeddedPlugin.destinationPath) { - name "compose-compiler" - type "jar" + // replace the standard jar with the one built by 'jarjar.repackage' in both api and runtime variants + apiElements.outgoing.artifacts.clear() + apiElements.outgoing.artifact(embeddedPlugin.destinationPath) { + builtBy embeddedPlugin + } + runtimeElements.outgoing.artifacts.clear() + runtimeElements.outgoing.artifact(embeddedPlugin.destinationPath) { builtBy embeddedPlugin } } diff --git a/compose/compose-runtime/compose-runtime-benchmark/build.gradle b/compose/compose-runtime/compose-runtime-benchmark/build.gradle index c04ad252955..44d34fed2d0 100644 --- a/compose/compose-runtime/compose-runtime-benchmark/build.gradle +++ b/compose/compose-runtime/compose-runtime-benchmark/build.gradle @@ -45,7 +45,7 @@ android { } dependencies { - kotlinPlugin project(path: ":compose:compose-compiler", configuration: "embeddablePlugin") + kotlinPlugin project(path: ":compose:compose-compiler") androidTestImplementation(project(":compose:compose-runtime")) androidTestImplementation(project(":ui:ui-core")) diff --git a/compose/compose-runtime/integration-tests/samples/build.gradle b/compose/compose-runtime/integration-tests/samples/build.gradle index 547e956f5d0..dad34379624 100644 --- a/compose/compose-runtime/integration-tests/samples/build.gradle +++ b/compose/compose-runtime/integration-tests/samples/build.gradle @@ -24,7 +24,7 @@ plugins { } dependencies { - kotlinPlugin project(path: ":compose:compose-compiler", configuration: "embeddablePlugin") + kotlinPlugin project(path: ":compose:compose-compiler") implementation(KOTLIN_STDLIB) diff --git a/core/core/api/restricted_1.3.0-alpha01.txt b/core/core/api/restricted_1.3.0-alpha01.txt index 9350f1d4833..7296a86d950 100644 --- a/core/core/api/restricted_1.3.0-alpha01.txt +++ b/core/core/api/restricted_1.3.0-alpha01.txt @@ -1932,11 +1932,11 @@ package androidx.core.util { method public static void buildShortClassTag(Object!, StringBuilder!); } - @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public class LogWriter extends java.io.Writer { - ctor public LogWriter(String!); - method public void close(); - method public void flush(); - method public void write(char[]!, int, int); + @Deprecated @RestrictTo(androidx.annotation.RestrictTo.Scope.TESTS) public class LogWriter extends java.io.Writer { + ctor @Deprecated public LogWriter(String!); + method @Deprecated public void close(); + method @Deprecated public void flush(); + method @Deprecated public void write(char[]!, int, int); } public class ObjectsCompat { diff --git a/core/core/api/restricted_current.txt b/core/core/api/restricted_current.txt index 9350f1d4833..7296a86d950 100644 --- a/core/core/api/restricted_current.txt +++ b/core/core/api/restricted_current.txt @@ -1932,11 +1932,11 @@ package androidx.core.util { method public static void buildShortClassTag(Object!, StringBuilder!); } - @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public class LogWriter extends java.io.Writer { - ctor public LogWriter(String!); - method public void close(); - method public void flush(); - method public void write(char[]!, int, int); + @Deprecated @RestrictTo(androidx.annotation.RestrictTo.Scope.TESTS) public class LogWriter extends java.io.Writer { + ctor @Deprecated public LogWriter(String!); + method @Deprecated public void close(); + method @Deprecated public void flush(); + method @Deprecated public void write(char[]!, int, int); } public class ObjectsCompat { diff --git a/core/core/src/main/java/androidx/core/util/LogWriter.java b/core/core/src/main/java/androidx/core/util/LogWriter.java index 435965948f0..bb3bcd25899 100644 --- a/core/core/src/main/java/androidx/core/util/LogWriter.java +++ b/core/core/src/main/java/androidx/core/util/LogWriter.java @@ -16,7 +16,7 @@ package androidx.core.util; -import static androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX; +import static androidx.annotation.RestrictTo.Scope.TESTS; import android.util.Log; @@ -25,11 +25,11 @@ import androidx.annotation.RestrictTo; import java.io.Writer; /** - * Helper for accessing features in {@link android.util.LogWriter}. - * * @hide + * @deprecated Copied to use sites. Do not use. */ -@RestrictTo(LIBRARY_GROUP_PREFIX) +@RestrictTo(TESTS) // Prevent new users. +@Deprecated public class LogWriter extends Writer { private final String mTag; private StringBuilder mBuilder = new StringBuilder(128); 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 3c35d639510..39c2cd76538 100644 --- a/fragment/fragment/src/main/java/androidx/fragment/app/BackStackRecord.java +++ b/fragment/fragment/src/main/java/androidx/fragment/app/BackStackRecord.java @@ -20,7 +20,6 @@ import android.util.Log; import androidx.annotation.NonNull; import androidx.annotation.Nullable; -import androidx.core.util.LogWriter; import androidx.lifecycle.Lifecycle; import java.io.PrintWriter; 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 9b29be47bea..6eb46f5e5fe 100644 --- a/fragment/fragment/src/main/java/androidx/fragment/app/FragmentManager.java +++ b/fragment/fragment/src/main/java/androidx/fragment/app/FragmentManager.java @@ -46,7 +46,6 @@ import androidx.annotation.RestrictTo; import androidx.annotation.StringRes; import androidx.collection.ArraySet; import androidx.core.os.CancellationSignal; -import androidx.core.util.LogWriter; import androidx.fragment.R; import androidx.lifecycle.Lifecycle; import androidx.lifecycle.LifecycleOwner; diff --git a/fragment/fragment/src/main/java/androidx/fragment/app/LogWriter.java b/fragment/fragment/src/main/java/androidx/fragment/app/LogWriter.java new file mode 100644 index 00000000000..91418cb3ccc --- /dev/null +++ b/fragment/fragment/src/main/java/androidx/fragment/app/LogWriter.java @@ -0,0 +1,60 @@ +/* + * Copyright 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package androidx.fragment.app; + +import android.util.Log; + +import java.io.Writer; + +final class LogWriter extends Writer { + private final String mTag; + private StringBuilder mBuilder = new StringBuilder(128); + + /** + * Create a new Writer that sends to the log with the given tag. + */ + LogWriter(String tag) { + mTag = tag; + } + + @Override public void close() { + flushBuilder(); + } + + @Override public void flush() { + flushBuilder(); + } + + @Override public void write(char[] buf, int offset, int count) { + for(int i = 0; i < count; i++) { + char c = buf[offset + i]; + if ( c == '\n') { + flushBuilder(); + } + else { + mBuilder.append(c); + } + } + } + + private void flushBuilder() { + if (mBuilder.length() > 0) { + Log.d(mTag, mBuilder.toString()); + mBuilder.delete(0, mBuilder.length()); + } + } +} diff --git a/jetifier/jetifier/migration.config b/jetifier/jetifier/migration.config index ee4583867e5..6496fe6fc21 100644 --- a/jetifier/jetifier/migration.config +++ b/jetifier/jetifier/migration.config @@ -425,6 +425,10 @@ "to": "ignore" }, { + "from": "androidx/fragment/app/LogWriter(.*)", + "to": "ignore" + }, + { "from": "androidx/gridlayout/widget/GridLayout(.*)", "to": "ignore" }, diff --git a/media2/widget/src/main/java/androidx/media2/widget/MediaControlView.java b/media2/widget/src/main/java/androidx/media2/widget/MediaControlView.java index f48ba8ec4b8..46c354dc4cb 100644 --- a/media2/widget/src/main/java/androidx/media2/widget/MediaControlView.java +++ b/media2/widget/src/main/java/androidx/media2/widget/MediaControlView.java @@ -87,48 +87,66 @@ import java.util.Locale; * internally create a MediaControlView instance and handle all the commands from buttons inside * MediaControlView. It is also possible to create a MediaControlView programmatically and add it * to a custom video view. For more information, refer to {@link VideoView}. - * - * By default, the buttons inside MediaControlView will not visible unless the corresponding + * <p> + * By default, the buttons inside MediaControlView will not be visible unless the corresponding * {@link SessionCommand} is marked as allowed. For more details, refer to {@link MediaSession}. * <p> - * <em> UI transitions : </em> + * <h3>UI transitions</h3> * Currently, MediaControlView animates UI transitions between three different modes: full, - * progress-bar only, and none. Full mode is where all the views are visible; Progress-bar only mode - * is where only the progress bar is visible and the title, transport controls and other icons are - * hidden; None mode is where all views are gone. The default interval between each mode is 2000ms, - * but it can be customized by using {@link VideoView#setMediaControlView(MediaControlView, long)}. + * progress-bar only, and none. + * <ul> + * <li><b>Full</b> mode is where all the views are visible + * <li><b>Progress-bar only</b> mode is where only the progress bar is visible and the title, + * transport controls and other icons are hidden + * <li><b>None</b> mode is where all views are gone. + * </ul> + * The default interval between each mode is 2000ms, but it can be customized by using + * {@link VideoView#setMediaControlView(MediaControlView, long)}. + * <p> * Transitions occur based on the following logic: - * 1) In Full mode - * a) If a touch/trackball event is received, will transition to None mode. - * b) If a touch/trackball event is not received, will transition to Progress-bar only mode - * after the interval. - * 2) In Progress-bar only mode - * a) If a touch/trackball event is received, will transition to Full mode. - * b) If a touch/trackball event is not received, will transition to None mode after the + * <ol> + * <li> In Full mode + * <ul> + * <li>If a touch/trackball event is received, will transition to None mode. + * <li>If a touch/trackball event is not received, will transition to Progress-bar only mode + * after the interval. + * </ul> + * <li> In Progress-bar only mode + * <ul> + * <li>If a touch/trackball event is received, will transition to Full mode. + * <li>If a touch/trackball event is not received, will transition to None mode after the * interval. - * 3) In None mode - * a) If a touch/trackball event is received, will transition to Full mode. - * 4) While animating, all touch/trackball event will be ignored. - * + * </ul> + * <li> In None mode, if a touch/trackball event is received, will transition to Full mode. + * </ol> + * While animating, all touch/trackball event will be ignored. * <p> - * <em> Customization : </em> + * <h3>Customization</h3> * In addition, the following customizations are supported: - * 1) Set focus to the play/pause button by calling {@link #requestPlayButtonFocus()}. - * 2) Set full screen behavior by calling {@link #setOnFullScreenListener(OnFullScreenListener)} + * <ul> + * <li>Set focus to the play/pause button by calling {@link #requestPlayButtonFocus()}. + * <li>Set full screen behavior by calling {@link #setOnFullScreenListener(OnFullScreenListener)}. + * Calling this method will also show the full screen button. + * </ul> * <p> - * <em> Displaying metadata : </em> + * <h3>Displaying metadata</h3> * MediaControlView supports displaying metadata by calling * {@link MediaItem#setMetadata(MediaMetadata)}. - * - * Metadata display is different for two different media types: music, and non-music. - * For music, the following metadata are supported: - * {@link MediaMetadata#METADATA_KEY_TITLE}, {@link MediaMetadata#METADATA_KEY_ARTIST}, - * and {@link MediaMetadata#METADATA_KEY_ALBUM_ART}. - * If values for these keys are not set, the following default values will be shown, respectively: - * {@link androidx.media2.widget.R.string#mcv2_music_title_unknown_text} - * {@link androidx.media2.widget.R.string#mcv2_music_artist_unknown_text} - * {@link androidx.media2.widget.R.drawable#media2_widget_ic_default_album_image} - * + * Metadata display is different for two different media types: music (sound only, with no video) + * and non-music (having video). + * <p> + * The following table shows the metadata displayed on VideoView and the default + * values assigned if the keys are not set: + * <table> + * <tr><th>Key</th><th>Default</th></tr> + * <tr><td>{@link MediaMetadata#METADATA_KEY_TITLE}</td> + * <td>{@link androidx.media2.widget.R.string#mcv2_music_title_unknown_text}</td></tr> + * <tr><td>{@link MediaMetadata#METADATA_KEY_ARTIST}</td> + * <td>{@link androidx.media2.widget.R.string#mcv2_music_artist_unknown_text}</td></tr> + * <tr><td>{@link MediaMetadata#METADATA_KEY_ALBUM_ART}</td> + * <td>{@link androidx.media2.widget.R.drawable#media2_widget_ic_default_album_image}</td></tr> + * </table> + * <p> * For non-music, only {@link MediaMetadata#METADATA_KEY_TITLE} metadata is supported. * If the value is not set, the following default value will be shown: * {@link androidx.media2.widget.R.string#mcv2_non_music_title_unknown_text} diff --git a/media2/widget/src/main/java/androidx/media2/widget/VideoView.java b/media2/widget/src/main/java/androidx/media2/widget/VideoView.java index 34585209f0c..cc2bd92104a 100644 --- a/media2/widget/src/main/java/androidx/media2/widget/VideoView.java +++ b/media2/widget/src/main/java/androidx/media2/widget/VideoView.java @@ -54,67 +54,108 @@ import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; /** - * A high level view for media playbacks that can be integrated with either a {@link SessionPlayer} + * A high level view for media playback that can be integrated with either a {@link SessionPlayer} * or a {@link MediaController}. Developers can easily implement a video rendering application - * using this class. + * using this class. By default, {@link MediaControlView} is attached so the playback + * control buttons are displayed on top of VideoView. * <p> - * For simple use cases not requiring communication with {@link MediaSession}, apps need to create - * a {@link SessionPlayer} (e.g. {@link androidx.media2.player.MediaPlayer}) and set it to this view - * by calling {@link #setPlayer}. - * For more advanced use cases that require {@link MediaSession} (e.g. handling media key events, - * integrating with other MediaSession apps as Assistant), apps need to create - * a {@link MediaController} attached to the {@link MediaSession} and set it to this view - * by calling {@link #setMediaController}. + * Contents: + * <ol> + * <li><a href="UseCases">Using VideoView with SessionPlayer or MediaController</a> + * <li><a href="UseWithMCV">Using VideoView with MediaControlView</a> + * <li><a href="ViewType">Choosing a view type</a> + * <li><a href="LegacyVideoView">Comparison with android.widget.VideoView</a> + * <li><a href="DisplayMetadata">Displaying Metadata</a> + * </ol> * - * <p> - * <em> View type can be selected : </em> - * VideoView can render videos on top of TextureView as well as - * SurfaceView selectively. The default is SurfaceView and it can be changed using - * {@link #setViewType(int)} method. Using SurfaceView is recommended in most cases for saving - * battery. TextureView might be preferred for supporting various UIs such as animation and - * translucency. - * - * <p> - * <em> Differences between {@link android.widget.VideoView android.widget.VideoView} class : </em> - * {@link VideoView} covers and inherits the most of - * {@link android.widget.VideoView android.widget.VideoView}'s functionality. The main differences - * are + * <h3 id="UseCases">Using VideoView with SessionPlayer or MediaController</h3> * <ul> - * <li> {@link VideoView} does not create a {@link android.media.MediaPlayer} instance while - * {@link android.widget.VideoView android.widget.VideoView} does. Instead, either a - * {@link SessionPlayer} or a {@link MediaController} instance should be created externally and set - * to {@link VideoView} using {@link #setPlayer(SessionPlayer)} or - * {@link #setMediaController(MediaController)}, respectively. - * <li> {@link VideoView} inherits ViewGroup and renders videos using SurfaceView and TextureView - * selectively while {@link android.widget.VideoView android.widget.VideoView} inherits SurfaceView - * class. - * <li> {@link VideoView} is integrated with {@link MediaControlView} and - * a default MediaControlView instance is attached to this VideoView by default. - * <li> If a developer wants to attach a custom {@link MediaControlView}, - * assign the custom media control widget using {@link #setMediaControlView}. - * <li> If {@link VideoView} communicates with {@link MediaSession} by calling - * {@link #setMediaController(MediaController)}, it will respond to media key events. + * <li> For simple use cases that do not require communication with a {@link MediaSession}, + * apps need to create a player instance that extends {@link SessionPlayer} (e.g. + * {@link androidx.media2.player.MediaPlayer}) and link it to this view by calling + * {@link #setPlayer}. + * <li> For more advanced use cases that require a {@link MediaSession} (e.g. handling media + * key events, integrating with other MediaSession apps as Assistant), apps need to create + * a {@link MediaController} that's attached to the {@link MediaSession} and link it to this + * view by calling {@link #setMediaController}. * </ul> * + * <h3 id="UseWithMCV">Using VideoView with MediaControlView</h3> + * {@link VideoView} is integrated with {@link MediaControlView} and a MediaControlView + * instance is attached to VideoView by default. * <p> - * <em> Displaying metadata : </em> - * VideoView supports displaying metadata for music by calling - * {@link MediaItem#setMetadata(MediaMetadata)}. Currently supported metadata are - * {@link MediaMetadata#METADATA_KEY_TITLE}, {@link MediaMetadata#METADATA_KEY_ARTIST}, - * and {@link MediaMetadata#METADATA_KEY_ALBUM_ART}. + * If you want to attach a custom {@link MediaControlView}, assign the custom media + * control widget using {@link #setMediaControlView}. + * <p> + * If you don't want to use {@link MediaControlView}, set + * the VideoView attribute {@link androidx.media2.widget.R.attr#enableControlView} to false. + * + * <h3 id="ViewType">Choosing a view type</h3> + * VideoView can render videos on a TextureView or SurfaceView. The + * default is SurfaceView which can be changed by using the {@link #setViewType(int)} method or + * by setting the {@link androidx.media2.widget.R.attr#viewType} attribute in the layout file. + * <p> Using SurfaceView is recommended in most cases for saving battery life. + * TextureView might be preferred for supporting various UIs such as animation and translucency. * - * If values for these keys are not set, the following default values will be shown, respectively: - * {@link androidx.media2.widget.R.string#mcv2_music_title_unknown_text} - * {@link androidx.media2.widget.R.string#mcv2_music_artist_unknown_text} - * {@link androidx.media2.widget.R.drawable#media2_widget_ic_default_album_image} + * <h3 id="LegacyVideoView">Comparison with android.widget.VideoView</h3> + * These are the main differences between the media2 VideoView widget and the older android widget: + * <ul> + * <li> + * {@link android.widget.VideoView android.widget.VideoView} creates a + * {@link android.media.MediaPlayer} instance internally and wraps playback APIs around it. + * <p> + * {@link VideoView androidx.media2.widget.VideoView} does not create a player instance + * internally. Instead, either a {@link SessionPlayer} or a {@link MediaController} instance + * should be created externally and link to {@link VideoView} using + * {@link #setPlayer(SessionPlayer)} or {@link #setMediaController(MediaController)}, + * respectively. + * <li> + * {@link android.widget.VideoView android.widget.VideoView} inherits from the SurfaceView + * class. + * <p> + * {@link VideoView androidx.media2.widget.VideoView} inherits from ViewGroup and can render + * videos using SurfaceView or TextureView, depending on your choice. + * <li> + * A {@link VideoView} can respond to media key events if you call {@link #setMediaController} + * to link it to a {@link MediaController} that's connected to an active {@link MediaSession}. + * </ul> * + * <h3 id="DisplayMetadata">Displaying Metadata</h3> + * When you play music only (sound with no video), VideoView can display album art and other + * metadata by calling {@link MediaItem#setMetadata(MediaMetadata)}. + * The following table shows the metadata displayed by the VideoView, and the default values + * assigned if the keys are not set: + * <table> + * <tr><th>Key</th><th>Default</th></tr> + * <tr><td>{@link MediaMetadata#METADATA_KEY_TITLE}</td> + * <td>{@link androidx.media2.widget.R.string#mcv2_music_title_unknown_text}</td></tr> + * <tr><td>{@link MediaMetadata#METADATA_KEY_ARTIST}</td> + * <td>{@link androidx.media2.widget.R.string#mcv2_music_artist_unknown_text}</td></tr> + * <tr><td>{@link MediaMetadata#METADATA_KEY_ALBUM_ART}</td> + * <td>{@link androidx.media2.widget.R.drawable#media2_widget_ic_default_album_image}</td></tr> + * </table> * <p> * Note: VideoView does not retain its full state when going into the background. In particular, it - * does not restore the current play state, play position, selected tracks. Applications should save - * and restore these on their own in {@link android.app.Activity#onSaveInstanceState} and + * does not save, and does not restore the current play state, play position, selected tracks. + * Applications should save and restore these on their own in + * {@link android.app.Activity#onSaveInstanceState} and * {@link android.app.Activity#onRestoreInstanceState}. - * {@link androidx.media2.widget.R.attr#enableControlView} - * {@link androidx.media2.widget.R.attr#viewType} + * <p> Attributes : + * <ul> + * <li> {@link androidx.media2.widget.R.attr#enableControlView} + * <li> {@link androidx.media2.widget.R.attr#viewType} + * </ul> + * <p> Example of attributes for a VideoView with TextureView and no attached control view: + * <pre> {@code + * <androidx.media2.widget.VideoView + * android:id="@+id/video_view" + * widget:enableControlView="false" + * widget:viewType="textureView" + * />}</pre> + * + * @see MediaControlView + * @see SessionPlayer + * @see MediaController */ public class VideoView extends SelectiveLayout { @IntDef({ diff --git a/ui/integration-tests/benchmark/build.gradle b/ui/integration-tests/benchmark/build.gradle index 0c0f31d5222..f04b08cb9b5 100644 --- a/ui/integration-tests/benchmark/build.gradle +++ b/ui/integration-tests/benchmark/build.gradle @@ -30,7 +30,7 @@ plugins { } dependencies { - kotlinPlugin project(path: ":compose:compose-compiler", configuration: "embeddablePlugin") + kotlinPlugin project(path: ":compose:compose-compiler") implementation project(":benchmark:benchmark-junit4") implementation project(":compose:compose-runtime") diff --git a/ui/integration-tests/test/build.gradle b/ui/integration-tests/test/build.gradle index aa9a0e2f471..8079c7eb20a 100644 --- a/ui/integration-tests/test/build.gradle +++ b/ui/integration-tests/test/build.gradle @@ -30,7 +30,7 @@ plugins { } dependencies { - kotlinPlugin project(path: ":compose:compose-compiler", configuration: "embeddablePlugin") + kotlinPlugin project(path: ":compose:compose-compiler") implementation(KOTLIN_STDLIB) diff --git a/ui/ui-android-view/build.gradle b/ui/ui-android-view/build.gradle index 9888349ee8e..62375be33cd 100644 --- a/ui/ui-android-view/build.gradle +++ b/ui/ui-android-view/build.gradle @@ -29,7 +29,7 @@ plugins { } dependencies { - kotlinPlugin project(path: ":compose:compose-compiler", configuration: "embeddablePlugin") + kotlinPlugin project(path: ":compose:compose-compiler") implementation(KOTLIN_COROUTINES_ANDROID) implementation(KOTLIN_STDLIB) diff --git a/ui/ui-animation-core/api/0.1.0-dev03.txt b/ui/ui-animation-core/api/0.1.0-dev03.txt index c790e823674..227080eafe8 100644 --- a/ui/ui-animation-core/api/0.1.0-dev03.txt +++ b/ui/ui-animation-core/api/0.1.0-dev03.txt @@ -210,6 +210,22 @@ package androidx.animation { method public androidx.animation.DurationBasedAnimation<T> build$lintWithKotlin(); } + public final class Spring { + ctor public Spring(); + field public static final androidx.animation.Spring.Companion! Companion; + field public static final float DampingRatioHighBouncy = 0.2f; + field public static final float DampingRatioLowBouncy = 0.75f; + field public static final float DampingRatioMediumBouncy = 0.5f; + field public static final float DampingRatioNoBouncy = 1.0f; + field public static final float StiffnessHigh = 10000.0f; + field public static final float StiffnessLow = 200.0f; + field public static final float StiffnessMedium = 1500.0f; + field public static final float StiffnessVeryLow = 50.0f; + } + + public static final class Spring.Companion { + } + public final class SpringSimulationKt { ctor public SpringSimulationKt(); } @@ -224,7 +240,7 @@ package androidx.animation { } public final class TransitionAnimation<T> implements androidx.animation.TransitionState { - ctor public TransitionAnimation(androidx.animation.TransitionDefinition<T> def, androidx.animation.AnimationClockObservable clock); + ctor public TransitionAnimation(androidx.animation.TransitionDefinition<T> def, androidx.animation.AnimationClockObservable clock, T? initState); method public operator <T> T! get(androidx.animation.PropKey<T> propKey); method public kotlin.jvm.functions.Function1<T,kotlin.Unit>? getOnStateChangeFinished(); method public kotlin.jvm.functions.Function0<kotlin.Unit>? getOnUpdate(); @@ -239,8 +255,6 @@ package androidx.animation { public final class TransitionDefinition<T> { ctor public TransitionDefinition(); - method public androidx.animation.TransitionAnimation<T> createAnimation(); - method public androidx.animation.TransitionAnimation<T> createAnimation(androidx.animation.AnimationClockObservable clock); method public androidx.animation.TransitionState getStateFor(T? name); method public void snapTransition(kotlin.Pair<? extends T,? extends T>![] fromToPairs, T? nextState = null); method public void state(T? name, kotlin.jvm.functions.Function1<? super androidx.animation.MutableTransitionState,kotlin.Unit> init); @@ -250,6 +264,8 @@ package androidx.animation { public final class TransitionDefinitionKt { ctor public TransitionDefinitionKt(); + method public static <T> androidx.animation.TransitionAnimation<T> createAnimation(androidx.animation.TransitionDefinition<T>); + method public static <T> androidx.animation.TransitionAnimation<T> createAnimation(androidx.animation.TransitionDefinition<T>, androidx.animation.AnimationClockObservable clock, T? initState = null); method public static <T> androidx.animation.TransitionDefinition<T> transitionDefinition(kotlin.jvm.functions.Function1<? super androidx.animation.TransitionDefinition<T>,kotlin.Unit> init); } diff --git a/ui/ui-animation-core/api/api_lint.ignore b/ui/ui-animation-core/api/api_lint.ignore index 523c32099d5..413d35162a2 100644 --- a/ui/ui-animation-core/api/api_lint.ignore +++ b/ui/ui-animation-core/api/api_lint.ignore @@ -57,3 +57,5 @@ MissingNullability: androidx.animation.FloatPropKey#interpolate(float, float, fl Missing nullability on method `interpolate` return MissingNullability: androidx.animation.IntPropKey#interpolate(int, int, float): Missing nullability on method `interpolate` return +MissingNullability: androidx.animation.Spring#Companion: + Missing nullability on field `Companion` in class `class androidx.animation.Spring` diff --git a/ui/ui-animation-core/api/current.txt b/ui/ui-animation-core/api/current.txt index c790e823674..227080eafe8 100644 --- a/ui/ui-animation-core/api/current.txt +++ b/ui/ui-animation-core/api/current.txt @@ -210,6 +210,22 @@ package androidx.animation { method public androidx.animation.DurationBasedAnimation<T> build$lintWithKotlin(); } + public final class Spring { + ctor public Spring(); + field public static final androidx.animation.Spring.Companion! Companion; + field public static final float DampingRatioHighBouncy = 0.2f; + field public static final float DampingRatioLowBouncy = 0.75f; + field public static final float DampingRatioMediumBouncy = 0.5f; + field public static final float DampingRatioNoBouncy = 1.0f; + field public static final float StiffnessHigh = 10000.0f; + field public static final float StiffnessLow = 200.0f; + field public static final float StiffnessMedium = 1500.0f; + field public static final float StiffnessVeryLow = 50.0f; + } + + public static final class Spring.Companion { + } + public final class SpringSimulationKt { ctor public SpringSimulationKt(); } @@ -224,7 +240,7 @@ package androidx.animation { } public final class TransitionAnimation<T> implements androidx.animation.TransitionState { - ctor public TransitionAnimation(androidx.animation.TransitionDefinition<T> def, androidx.animation.AnimationClockObservable clock); + ctor public TransitionAnimation(androidx.animation.TransitionDefinition<T> def, androidx.animation.AnimationClockObservable clock, T? initState); method public operator <T> T! get(androidx.animation.PropKey<T> propKey); method public kotlin.jvm.functions.Function1<T,kotlin.Unit>? getOnStateChangeFinished(); method public kotlin.jvm.functions.Function0<kotlin.Unit>? getOnUpdate(); @@ -239,8 +255,6 @@ package androidx.animation { public final class TransitionDefinition<T> { ctor public TransitionDefinition(); - method public androidx.animation.TransitionAnimation<T> createAnimation(); - method public androidx.animation.TransitionAnimation<T> createAnimation(androidx.animation.AnimationClockObservable clock); method public androidx.animation.TransitionState getStateFor(T? name); method public void snapTransition(kotlin.Pair<? extends T,? extends T>![] fromToPairs, T? nextState = null); method public void state(T? name, kotlin.jvm.functions.Function1<? super androidx.animation.MutableTransitionState,kotlin.Unit> init); @@ -250,6 +264,8 @@ package androidx.animation { public final class TransitionDefinitionKt { ctor public TransitionDefinitionKt(); + method public static <T> androidx.animation.TransitionAnimation<T> createAnimation(androidx.animation.TransitionDefinition<T>); + method public static <T> androidx.animation.TransitionAnimation<T> createAnimation(androidx.animation.TransitionDefinition<T>, androidx.animation.AnimationClockObservable clock, T? initState = null); method public static <T> androidx.animation.TransitionDefinition<T> transitionDefinition(kotlin.jvm.functions.Function1<? super androidx.animation.TransitionDefinition<T>,kotlin.Unit> init); } diff --git a/ui/ui-animation-core/api/public_plus_experimental_0.1.0-dev03.txt b/ui/ui-animation-core/api/public_plus_experimental_0.1.0-dev03.txt index c790e823674..227080eafe8 100644 --- a/ui/ui-animation-core/api/public_plus_experimental_0.1.0-dev03.txt +++ b/ui/ui-animation-core/api/public_plus_experimental_0.1.0-dev03.txt @@ -210,6 +210,22 @@ package androidx.animation { method public androidx.animation.DurationBasedAnimation<T> build$lintWithKotlin(); } + public final class Spring { + ctor public Spring(); + field public static final androidx.animation.Spring.Companion! Companion; + field public static final float DampingRatioHighBouncy = 0.2f; + field public static final float DampingRatioLowBouncy = 0.75f; + field public static final float DampingRatioMediumBouncy = 0.5f; + field public static final float DampingRatioNoBouncy = 1.0f; + field public static final float StiffnessHigh = 10000.0f; + field public static final float StiffnessLow = 200.0f; + field public static final float StiffnessMedium = 1500.0f; + field public static final float StiffnessVeryLow = 50.0f; + } + + public static final class Spring.Companion { + } + public final class SpringSimulationKt { ctor public SpringSimulationKt(); } @@ -224,7 +240,7 @@ package androidx.animation { } public final class TransitionAnimation<T> implements androidx.animation.TransitionState { - ctor public TransitionAnimation(androidx.animation.TransitionDefinition<T> def, androidx.animation.AnimationClockObservable clock); + ctor public TransitionAnimation(androidx.animation.TransitionDefinition<T> def, androidx.animation.AnimationClockObservable clock, T? initState); method public operator <T> T! get(androidx.animation.PropKey<T> propKey); method public kotlin.jvm.functions.Function1<T,kotlin.Unit>? getOnStateChangeFinished(); method public kotlin.jvm.functions.Function0<kotlin.Unit>? getOnUpdate(); @@ -239,8 +255,6 @@ package androidx.animation { public final class TransitionDefinition<T> { ctor public TransitionDefinition(); - method public androidx.animation.TransitionAnimation<T> createAnimation(); - method public androidx.animation.TransitionAnimation<T> createAnimation(androidx.animation.AnimationClockObservable clock); method public androidx.animation.TransitionState getStateFor(T? name); method public void snapTransition(kotlin.Pair<? extends T,? extends T>![] fromToPairs, T? nextState = null); method public void state(T? name, kotlin.jvm.functions.Function1<? super androidx.animation.MutableTransitionState,kotlin.Unit> init); @@ -250,6 +264,8 @@ package androidx.animation { public final class TransitionDefinitionKt { ctor public TransitionDefinitionKt(); + method public static <T> androidx.animation.TransitionAnimation<T> createAnimation(androidx.animation.TransitionDefinition<T>); + method public static <T> androidx.animation.TransitionAnimation<T> createAnimation(androidx.animation.TransitionDefinition<T>, androidx.animation.AnimationClockObservable clock, T? initState = null); method public static <T> androidx.animation.TransitionDefinition<T> transitionDefinition(kotlin.jvm.functions.Function1<? super androidx.animation.TransitionDefinition<T>,kotlin.Unit> init); } diff --git a/ui/ui-animation-core/api/public_plus_experimental_current.txt b/ui/ui-animation-core/api/public_plus_experimental_current.txt index c790e823674..227080eafe8 100644 --- a/ui/ui-animation-core/api/public_plus_experimental_current.txt +++ b/ui/ui-animation-core/api/public_plus_experimental_current.txt @@ -210,6 +210,22 @@ package androidx.animation { method public androidx.animation.DurationBasedAnimation<T> build$lintWithKotlin(); } + public final class Spring { + ctor public Spring(); + field public static final androidx.animation.Spring.Companion! Companion; + field public static final float DampingRatioHighBouncy = 0.2f; + field public static final float DampingRatioLowBouncy = 0.75f; + field public static final float DampingRatioMediumBouncy = 0.5f; + field public static final float DampingRatioNoBouncy = 1.0f; + field public static final float StiffnessHigh = 10000.0f; + field public static final float StiffnessLow = 200.0f; + field public static final float StiffnessMedium = 1500.0f; + field public static final float StiffnessVeryLow = 50.0f; + } + + public static final class Spring.Companion { + } + public final class SpringSimulationKt { ctor public SpringSimulationKt(); } @@ -224,7 +240,7 @@ package androidx.animation { } public final class TransitionAnimation<T> implements androidx.animation.TransitionState { - ctor public TransitionAnimation(androidx.animation.TransitionDefinition<T> def, androidx.animation.AnimationClockObservable clock); + ctor public TransitionAnimation(androidx.animation.TransitionDefinition<T> def, androidx.animation.AnimationClockObservable clock, T? initState); method public operator <T> T! get(androidx.animation.PropKey<T> propKey); method public kotlin.jvm.functions.Function1<T,kotlin.Unit>? getOnStateChangeFinished(); method public kotlin.jvm.functions.Function0<kotlin.Unit>? getOnUpdate(); @@ -239,8 +255,6 @@ package androidx.animation { public final class TransitionDefinition<T> { ctor public TransitionDefinition(); - method public androidx.animation.TransitionAnimation<T> createAnimation(); - method public androidx.animation.TransitionAnimation<T> createAnimation(androidx.animation.AnimationClockObservable clock); method public androidx.animation.TransitionState getStateFor(T? name); method public void snapTransition(kotlin.Pair<? extends T,? extends T>![] fromToPairs, T? nextState = null); method public void state(T? name, kotlin.jvm.functions.Function1<? super androidx.animation.MutableTransitionState,kotlin.Unit> init); @@ -250,6 +264,8 @@ package androidx.animation { public final class TransitionDefinitionKt { ctor public TransitionDefinitionKt(); + method public static <T> androidx.animation.TransitionAnimation<T> createAnimation(androidx.animation.TransitionDefinition<T>); + method public static <T> androidx.animation.TransitionAnimation<T> createAnimation(androidx.animation.TransitionDefinition<T>, androidx.animation.AnimationClockObservable clock, T? initState = null); method public static <T> androidx.animation.TransitionDefinition<T> transitionDefinition(kotlin.jvm.functions.Function1<? super androidx.animation.TransitionDefinition<T>,kotlin.Unit> init); } diff --git a/ui/ui-animation-core/api/restricted_0.1.0-dev03.txt b/ui/ui-animation-core/api/restricted_0.1.0-dev03.txt index c790e823674..227080eafe8 100644 --- a/ui/ui-animation-core/api/restricted_0.1.0-dev03.txt +++ b/ui/ui-animation-core/api/restricted_0.1.0-dev03.txt @@ -210,6 +210,22 @@ package androidx.animation { method public androidx.animation.DurationBasedAnimation<T> build$lintWithKotlin(); } + public final class Spring { + ctor public Spring(); + field public static final androidx.animation.Spring.Companion! Companion; + field public static final float DampingRatioHighBouncy = 0.2f; + field public static final float DampingRatioLowBouncy = 0.75f; + field public static final float DampingRatioMediumBouncy = 0.5f; + field public static final float DampingRatioNoBouncy = 1.0f; + field public static final float StiffnessHigh = 10000.0f; + field public static final float StiffnessLow = 200.0f; + field public static final float StiffnessMedium = 1500.0f; + field public static final float StiffnessVeryLow = 50.0f; + } + + public static final class Spring.Companion { + } + public final class SpringSimulationKt { ctor public SpringSimulationKt(); } @@ -224,7 +240,7 @@ package androidx.animation { } public final class TransitionAnimation<T> implements androidx.animation.TransitionState { - ctor public TransitionAnimation(androidx.animation.TransitionDefinition<T> def, androidx.animation.AnimationClockObservable clock); + ctor public TransitionAnimation(androidx.animation.TransitionDefinition<T> def, androidx.animation.AnimationClockObservable clock, T? initState); method public operator <T> T! get(androidx.animation.PropKey<T> propKey); method public kotlin.jvm.functions.Function1<T,kotlin.Unit>? getOnStateChangeFinished(); method public kotlin.jvm.functions.Function0<kotlin.Unit>? getOnUpdate(); @@ -239,8 +255,6 @@ package androidx.animation { public final class TransitionDefinition<T> { ctor public TransitionDefinition(); - method public androidx.animation.TransitionAnimation<T> createAnimation(); - method public androidx.animation.TransitionAnimation<T> createAnimation(androidx.animation.AnimationClockObservable clock); method public androidx.animation.TransitionState getStateFor(T? name); method public void snapTransition(kotlin.Pair<? extends T,? extends T>![] fromToPairs, T? nextState = null); method public void state(T? name, kotlin.jvm.functions.Function1<? super androidx.animation.MutableTransitionState,kotlin.Unit> init); @@ -250,6 +264,8 @@ package androidx.animation { public final class TransitionDefinitionKt { ctor public TransitionDefinitionKt(); + method public static <T> androidx.animation.TransitionAnimation<T> createAnimation(androidx.animation.TransitionDefinition<T>); + method public static <T> androidx.animation.TransitionAnimation<T> createAnimation(androidx.animation.TransitionDefinition<T>, androidx.animation.AnimationClockObservable clock, T? initState = null); method public static <T> androidx.animation.TransitionDefinition<T> transitionDefinition(kotlin.jvm.functions.Function1<? super androidx.animation.TransitionDefinition<T>,kotlin.Unit> init); } diff --git a/ui/ui-animation-core/api/restricted_current.txt b/ui/ui-animation-core/api/restricted_current.txt index c790e823674..227080eafe8 100644 --- a/ui/ui-animation-core/api/restricted_current.txt +++ b/ui/ui-animation-core/api/restricted_current.txt @@ -210,6 +210,22 @@ package androidx.animation { method public androidx.animation.DurationBasedAnimation<T> build$lintWithKotlin(); } + public final class Spring { + ctor public Spring(); + field public static final androidx.animation.Spring.Companion! Companion; + field public static final float DampingRatioHighBouncy = 0.2f; + field public static final float DampingRatioLowBouncy = 0.75f; + field public static final float DampingRatioMediumBouncy = 0.5f; + field public static final float DampingRatioNoBouncy = 1.0f; + field public static final float StiffnessHigh = 10000.0f; + field public static final float StiffnessLow = 200.0f; + field public static final float StiffnessMedium = 1500.0f; + field public static final float StiffnessVeryLow = 50.0f; + } + + public static final class Spring.Companion { + } + public final class SpringSimulationKt { ctor public SpringSimulationKt(); } @@ -224,7 +240,7 @@ package androidx.animation { } public final class TransitionAnimation<T> implements androidx.animation.TransitionState { - ctor public TransitionAnimation(androidx.animation.TransitionDefinition<T> def, androidx.animation.AnimationClockObservable clock); + ctor public TransitionAnimation(androidx.animation.TransitionDefinition<T> def, androidx.animation.AnimationClockObservable clock, T? initState); method public operator <T> T! get(androidx.animation.PropKey<T> propKey); method public kotlin.jvm.functions.Function1<T,kotlin.Unit>? getOnStateChangeFinished(); method public kotlin.jvm.functions.Function0<kotlin.Unit>? getOnUpdate(); @@ -239,8 +255,6 @@ package androidx.animation { public final class TransitionDefinition<T> { ctor public TransitionDefinition(); - method public androidx.animation.TransitionAnimation<T> createAnimation(); - method public androidx.animation.TransitionAnimation<T> createAnimation(androidx.animation.AnimationClockObservable clock); method public androidx.animation.TransitionState getStateFor(T? name); method public void snapTransition(kotlin.Pair<? extends T,? extends T>![] fromToPairs, T? nextState = null); method public void state(T? name, kotlin.jvm.functions.Function1<? super androidx.animation.MutableTransitionState,kotlin.Unit> init); @@ -250,6 +264,8 @@ package androidx.animation { public final class TransitionDefinitionKt { ctor public TransitionDefinitionKt(); + method public static <T> androidx.animation.TransitionAnimation<T> createAnimation(androidx.animation.TransitionDefinition<T>); + method public static <T> androidx.animation.TransitionAnimation<T> createAnimation(androidx.animation.TransitionDefinition<T>, androidx.animation.AnimationClockObservable clock, T? initState = null); method public static <T> androidx.animation.TransitionDefinition<T> transitionDefinition(kotlin.jvm.functions.Function1<? super androidx.animation.TransitionDefinition<T>,kotlin.Unit> init); } diff --git a/ui/ui-animation-core/integration-tests/samples/build.gradle b/ui/ui-animation-core/integration-tests/samples/build.gradle index 35576712b2e..cc0bdb850c2 100644 --- a/ui/ui-animation-core/integration-tests/samples/build.gradle +++ b/ui/ui-animation-core/integration-tests/samples/build.gradle @@ -26,7 +26,7 @@ plugins { } dependencies { - kotlinPlugin project(path: ":compose:compose-compiler", configuration: "embeddablePlugin") + kotlinPlugin project(path: ":compose:compose-compiler") implementation(KOTLIN_STDLIB) diff --git a/ui/ui-animation-core/src/main/java/androidx/animation/Animation.kt b/ui/ui-animation-core/src/main/java/androidx/animation/Animation.kt index 2bd4c9c1083..df591772f74 100644 --- a/ui/ui-animation-core/src/main/java/androidx/animation/Animation.kt +++ b/ui/ui-animation-core/src/main/java/androidx/animation/Animation.kt @@ -16,8 +16,6 @@ package androidx.animation -import androidx.animation.Physics.Companion.DampingRatioNoBouncy -import androidx.animation.Physics.Companion.StiffnessVeryLow import kotlin.math.min const val DEBUG = false @@ -212,31 +210,19 @@ internal fun <T> Snap(): DurationBasedAnimation<T> = Tween(0L, 0L, LinearEasing) /** - * [Physics] animation is in its core a spring animation. It is the default animation that the - * animation system uses to createAnimation from [TransitionState] to [TransitionState] when no - * animations are specified. Its configuration can be tuned via adjusting the spring parameters, - * namely [dampingRatio] and [stiffness]. By default, [Physics] animation uses a spring with - * [dampingRatio] = [DampingRatioNoBouncy] and [stiffness] = [StiffnessVeryLow]. + * Physics class contains a number of recommended configurations for physics animations. */ -internal class Physics<T>( - /** - * Damping ratio of the spring. Defaults to [DampingRatioNoBouncy] - */ - dampingRatio: Float = DampingRatioNoBouncy, - /** - * Stiffness of the spring. Defaults to [StiffnessVeryLow] - */ - stiffness: Float = StiffnessVeryLow -) : Animation<T> { - - // TODO: Make all of these consts public. +// TODO: Consider making all animations public, and fold this companion object into +// SpringAnimation. +class Spring { companion object { /** * Stiffness constant for extremely stiff spring */ const val StiffnessHigh = 10_000f /** - * Stiffness constant for medium stiff spring. This is the default stiffness for spring force. + * Stiffness constant for medium stiff spring. This is the default stiffness for spring + * force. */ const val StiffnessMedium = 1500f /** @@ -254,9 +240,9 @@ internal class Physics<T>( */ const val DampingRatioHighBouncy = 0.2f /** - * Damping ratio for a medium bouncy spring. This is also the default damping ratio for spring - * force. Note for under-damped springs (i.e. damping ratio < 1), the lower the damping ratio, - * the more bouncy the spring. + * Damping ratio for a medium bouncy spring. This is also the default damping ratio for + * spring force. Note for under-damped springs (i.e. damping ratio < 1), the lower the + * damping ratio, the more bouncy the spring. */ const val DampingRatioMediumBouncy = 0.5f /** @@ -265,12 +251,31 @@ internal class Physics<T>( */ const val DampingRatioLowBouncy = 0.75f /** - * Damping ratio for a spring with no bounciness. This damping ratio will create a critically - * damped spring that returns to equilibrium within the shortest amount of time without - * oscillating. + * Damping ratio for a spring with no bounciness. This damping ratio will create a + * critically damped spring that returns to equilibrium within the shortest amount of time + * without oscillating. */ const val DampingRatioNoBouncy = 1f } +} + +/** + * [SpringAnimation] animation is in its core a spring animation. It is the default animation that + * the animation system uses to createAnimation from [TransitionState] to [TransitionState] when no + * animations are specified. Its configuration can be tuned via adjusting the spring parameters, + * namely dampingRatio and stiffness. By default, [SpringAnimation] animation uses a spring with + * dampingRatio = [Spring.DampingRatioNoBouncy] and stiffness = [Spring.StiffnessVeryLow]. + */ +internal class SpringAnimation<T>( + /** + * Damping ratio of the spring. Defaults to [Spring.DampingRatioNoBouncy] + */ + dampingRatio: Float = Spring.DampingRatioNoBouncy, + /** + * Stiffness of the spring. Defaults to [Spring.StiffnessVeryLow] + */ + stiffness: Float = Spring.StiffnessMedium +) : Animation<T> { private val spring = SpringSimulation(1f).also { it.dampingRatio = dampingRatio diff --git a/ui/ui-animation-core/src/main/java/androidx/animation/AnimationBuilder.kt b/ui/ui-animation-core/src/main/java/androidx/animation/AnimationBuilder.kt index 505c5e046cc..48badce7d27 100644 --- a/ui/ui-animation-core/src/main/java/androidx/animation/AnimationBuilder.kt +++ b/ui/ui-animation-core/src/main/java/androidx/animation/AnimationBuilder.kt @@ -16,8 +16,8 @@ package androidx.animation -import androidx.animation.Physics.Companion.DampingRatioNoBouncy -import androidx.animation.Physics.Companion.StiffnessVeryLow +import androidx.animation.Spring.Companion.DampingRatioNoBouncy +import androidx.animation.Spring.Companion.StiffnessVeryLow abstract class AnimationBuilder<T> { internal abstract fun build(): Animation<T> @@ -180,7 +180,7 @@ open class PhysicsBuilder<T>( ) : AnimationBuilder<T>() { override fun build(): Animation<T> = - Physics(dampingRatio, stiffness) + SpringAnimation(dampingRatio, stiffness) } /** diff --git a/ui/ui-animation-core/src/main/java/androidx/animation/SpringSimulation.kt b/ui/ui-animation-core/src/main/java/androidx/animation/SpringSimulation.kt index 8a3f6478f63..91d620f47a2 100644 --- a/ui/ui-animation-core/src/main/java/androidx/animation/SpringSimulation.kt +++ b/ui/ui-animation-core/src/main/java/androidx/animation/SpringSimulation.kt @@ -47,7 +47,7 @@ internal val UNSET = Float.MAX_VALUE internal class SpringSimulation(var finalPosition: Float) { // Natural frequency - private var naturalFreq = Math.sqrt(Physics.StiffnessVeryLow.toDouble()) + private var naturalFreq = Math.sqrt(Spring.StiffnessVeryLow.toDouble()) // Indicates whether the spring has been initialized private var initialized = false @@ -83,7 +83,7 @@ internal class SpringSimulation(var finalPosition: Float) { * * @return damping ratio of the spring */ - var dampingRatio: Float = Physics.DampingRatioNoBouncy + var dampingRatio: Float = Spring.DampingRatioNoBouncy set(value) { if (value < 0) { throw IllegalArgumentException("Damping ratio must be non-negative") diff --git a/ui/ui-animation-core/src/main/java/androidx/animation/TransitionAnimation.kt b/ui/ui-animation-core/src/main/java/androidx/animation/TransitionAnimation.kt index 199bc9b5dfa..cfa72b506aa 100644 --- a/ui/ui-animation-core/src/main/java/androidx/animation/TransitionAnimation.kt +++ b/ui/ui-animation-core/src/main/java/androidx/animation/TransitionAnimation.kt @@ -24,7 +24,7 @@ import androidx.animation.InterruptionHandling.UNINTERRUPTIBLE * [TransitionState]) to another. More specifically, it reads the property values out of the new * state that it is going to, as well as the animations defined for the properties, and run these * animations until all properties have reached their pre-defined values in the new state. When no - * animation is specified for a property, a default [Physics] animation will be used. + * animation is specified for a property, a default [SpringAnimation] animation will be used. * * [TransitionAnimation] may be interrupted while the animation is on-going by a request to go * to another state. [TransitionAnimation] ensures that all the animating properties preserve their @@ -35,7 +35,8 @@ import androidx.animation.InterruptionHandling.UNINTERRUPTIBLE */ class TransitionAnimation<T> ( private val def: TransitionDefinition<T>, - private val clock: AnimationClockObservable + private val clock: AnimationClockObservable, + initState: T? = null ) : TransitionState { var onUpdate: (() -> Unit)? = null @@ -61,10 +62,17 @@ class TransitionAnimation<T> ( // TODO("Create a more efficient code path for default only transition def") init { - currentState = AnimationState(def.defaultState, def.defaultState.name) + // If an initial state is specified in the ctor, use that instead of the default state. + val defaultState: StateImpl<T> + if (initState == null) { + defaultState = def.defaultState + } else { + defaultState = def.states[initState]!! + } + currentState = AnimationState(defaultState, defaultState.name) // Need to come up with a better plan to avoid the foot gun of accidentally modifying state - fromState = def.defaultState - toState = def.defaultState + fromState = defaultState + toState = defaultState } // Interpolate current state and the new state @@ -96,13 +104,14 @@ class TransitionAnimation<T> ( // props in each state. } - startAnimation() - fromState = AnimationState(currentState, toState.name) toState = newState if (DEBUG) { Log.w("TransAnim", "Animating to new state: ${toState.name}") } + + // Start animation should be called after all the setup has been done + startAnimation() } private fun getPlayTime(): Long { diff --git a/ui/ui-animation-core/src/main/java/androidx/animation/TransitionDefinition.kt b/ui/ui-animation-core/src/main/java/androidx/animation/TransitionDefinition.kt index 9a85ff3cfcc..48162a815f4 100644 --- a/ui/ui-animation-core/src/main/java/androidx/animation/TransitionDefinition.kt +++ b/ui/ui-animation-core/src/main/java/androidx/animation/TransitionDefinition.kt @@ -23,7 +23,7 @@ import kotlin.experimental.ExperimentalTypeInference * * Each property involved in the states that the transition is from and to can have an animation * associated with it. When such an animation is defined, the animation system will be using it - * instead of the default [Physics] animation to createAnimation the value change for that property. + * instead of the default [SpringAnimation] animation to createAnimation the value change for that property. * * @sample androidx.animation.samples.TransitionSpecWith3Properties **/ @@ -50,7 +50,7 @@ class TransitionSpec<S> internal constructor(private val fromToPairs: Array<out /** * The default animation to use when it wasn't explicitly provided for a property */ - internal var defaultAnimation: () -> Animation<Any> = { Physics() } + internal var defaultAnimation: () -> Animation<Any> = { SpringAnimation() } private val propAnimation: MutableMap<PropKey<*>, Animation<*>> = mutableMapOf() internal fun <T> getAnimationForProp(prop: PropKey<T>): Animation<T> { @@ -79,9 +79,9 @@ class TransitionSpec<S> internal constructor(private val fromToPairs: Array<out TweenBuilder<T>().apply(init) /** - * Creates a [Physics] animation, initialized with [init] + * Creates a [SpringAnimation] animation, initialized with [init] * - * @param init Initialization function for the [Physics] animation + * @param init Initialization function for the [SpringAnimation] animation */ fun <T> physics(init: PhysicsBuilder<T>.() -> Unit): AnimationBuilder<T> = PhysicsBuilder<T>().apply(init) @@ -198,18 +198,6 @@ class TransitionDefinition<T> { } /** - * Creates a transition animation using the transition definition. - */ - fun createAnimation() = TransitionAnimation(this, DefaultAnimationClock()) - - /** - * Creates a transition animation using the transition definition and the given clock. - * - * @param clock The clock source for animation to get frame time from. - */ - fun createAnimation(clock: AnimationClockObservable) = TransitionAnimation(this, clock) - - /** * Returns a state holder for the specific state [name]. Useful for the cases * where we don't need actual animation to be happening like in tests. */ @@ -217,6 +205,23 @@ class TransitionDefinition<T> { } /** + * Creates a transition animation using the transition definition. + * // TODO: Ripple impl needs to pass the ambient here clock, then we can remove this function. + */ +fun <T> TransitionDefinition<T>.createAnimation() = + TransitionAnimation(this, DefaultAnimationClock()) + +/** + * Creates a transition animation using the transition definition and the given clock. + * + * @param clock The clock source for animation to get frame time from. + */ +fun <T> TransitionDefinition<T>.createAnimation( + clock: AnimationClockObservable, + initState: T? = null +) = TransitionAnimation(this, clock, initState) + +/** * Creates a [TransitionDefinition] using the [init] function to initialize it. * * @param init Initialization function for the [TransitionDefinition] diff --git a/ui/ui-animation-core/src/test/java/androidx/animation/TransitionAnimationTest.kt b/ui/ui-animation-core/src/test/java/androidx/animation/TransitionAnimationTest.kt index bdfb631d788..a151099627a 100644 --- a/ui/ui-animation-core/src/test/java/androidx/animation/TransitionAnimationTest.kt +++ b/ui/ui-animation-core/src/test/java/androidx/animation/TransitionAnimationTest.kt @@ -28,7 +28,7 @@ class TransitionAnimationTest { val clock = ManualAnimationClock(0) val anim = TransitionAnimation(def1, clock) anim.toState(AnimState.B) - val physicsAnim = Physics<Float>() + val physicsAnim = SpringAnimation<Float>() var playTime = 0L do { // Increment the time stamp until the animation finishes @@ -43,6 +43,33 @@ class TransitionAnimationTest { playTime += 20L } while (anim.isRunning) } + + @Test + fun testInitialState() { + val clock = ManualAnimationClock(0) + val anim = TransitionAnimation(def1, clock, AnimState.C) + assertEquals(anim[prop1], 1000f) + assertEquals(anim[prop2], -250f) + } + + @Test + fun testStateChangedListener() { + val clock = ManualAnimationClock(0) + val anim = TransitionAnimation(def1, clock, AnimState.C) + var lastState: AnimState? = null + anim.onStateChangeFinished = { + lastState = it + } + anim.toState(AnimState.A) + // Increment the clock by some large amount to guarantee the finish of the animation + clock.clockTimeMillis += 100000 + assertEquals(AnimState.A, lastState) + + anim.toState(AnimState.B) + // Increment the clock by some large amount to guarantee the finish of the animation + clock.clockTimeMillis += 100000 + assertEquals(AnimState.B, lastState) + } } private enum class AnimState { @@ -62,4 +89,9 @@ private val def1 = transitionDefinition { this[prop1] = 1f this[prop2] = -100f } + + state(AnimState.C) { + this[prop1] = 1000f + this[prop2] = -250f + } }
\ No newline at end of file diff --git a/ui/ui-animation/api/0.1.0-dev03.txt b/ui/ui-animation/api/0.1.0-dev03.txt index 113f0be5a55..e1311a33597 100644 --- a/ui/ui-animation/api/0.1.0-dev03.txt +++ b/ui/ui-animation/api/0.1.0-dev03.txt @@ -35,7 +35,7 @@ package androidx.ui.animation { public final class TransitionKt { ctor public TransitionKt(); - method public static <T> void Transition(androidx.animation.TransitionDefinition<T> definition, T? toState, androidx.animation.AnimationClockObservable clock = +ambient(AnimationClockAmbient), kotlin.jvm.functions.Function1<? super androidx.animation.TransitionState,kotlin.Unit> children); + method public static <T> void Transition(androidx.animation.TransitionDefinition<T> definition, T? toState, androidx.animation.AnimationClockObservable clock = +ambient(AnimationClockAmbient), kotlin.jvm.functions.Function1<? super T,kotlin.Unit>? onStateChangeFinished = null, kotlin.jvm.functions.Function1<? super androidx.animation.TransitionState,kotlin.Unit> children); method public static boolean getTransitionsEnabled(); method public static void setTransitionsEnabled(boolean p); } diff --git a/ui/ui-animation/api/current.txt b/ui/ui-animation/api/current.txt index 113f0be5a55..e1311a33597 100644 --- a/ui/ui-animation/api/current.txt +++ b/ui/ui-animation/api/current.txt @@ -35,7 +35,7 @@ package androidx.ui.animation { public final class TransitionKt { ctor public TransitionKt(); - method public static <T> void Transition(androidx.animation.TransitionDefinition<T> definition, T? toState, androidx.animation.AnimationClockObservable clock = +ambient(AnimationClockAmbient), kotlin.jvm.functions.Function1<? super androidx.animation.TransitionState,kotlin.Unit> children); + method public static <T> void Transition(androidx.animation.TransitionDefinition<T> definition, T? toState, androidx.animation.AnimationClockObservable clock = +ambient(AnimationClockAmbient), kotlin.jvm.functions.Function1<? super T,kotlin.Unit>? onStateChangeFinished = null, kotlin.jvm.functions.Function1<? super androidx.animation.TransitionState,kotlin.Unit> children); method public static boolean getTransitionsEnabled(); method public static void setTransitionsEnabled(boolean p); } diff --git a/ui/ui-animation/api/public_plus_experimental_0.1.0-dev03.txt b/ui/ui-animation/api/public_plus_experimental_0.1.0-dev03.txt index 113f0be5a55..e1311a33597 100644 --- a/ui/ui-animation/api/public_plus_experimental_0.1.0-dev03.txt +++ b/ui/ui-animation/api/public_plus_experimental_0.1.0-dev03.txt @@ -35,7 +35,7 @@ package androidx.ui.animation { public final class TransitionKt { ctor public TransitionKt(); - method public static <T> void Transition(androidx.animation.TransitionDefinition<T> definition, T? toState, androidx.animation.AnimationClockObservable clock = +ambient(AnimationClockAmbient), kotlin.jvm.functions.Function1<? super androidx.animation.TransitionState,kotlin.Unit> children); + method public static <T> void Transition(androidx.animation.TransitionDefinition<T> definition, T? toState, androidx.animation.AnimationClockObservable clock = +ambient(AnimationClockAmbient), kotlin.jvm.functions.Function1<? super T,kotlin.Unit>? onStateChangeFinished = null, kotlin.jvm.functions.Function1<? super androidx.animation.TransitionState,kotlin.Unit> children); method public static boolean getTransitionsEnabled(); method public static void setTransitionsEnabled(boolean p); } diff --git a/ui/ui-animation/api/public_plus_experimental_current.txt b/ui/ui-animation/api/public_plus_experimental_current.txt index 113f0be5a55..e1311a33597 100644 --- a/ui/ui-animation/api/public_plus_experimental_current.txt +++ b/ui/ui-animation/api/public_plus_experimental_current.txt @@ -35,7 +35,7 @@ package androidx.ui.animation { public final class TransitionKt { ctor public TransitionKt(); - method public static <T> void Transition(androidx.animation.TransitionDefinition<T> definition, T? toState, androidx.animation.AnimationClockObservable clock = +ambient(AnimationClockAmbient), kotlin.jvm.functions.Function1<? super androidx.animation.TransitionState,kotlin.Unit> children); + method public static <T> void Transition(androidx.animation.TransitionDefinition<T> definition, T? toState, androidx.animation.AnimationClockObservable clock = +ambient(AnimationClockAmbient), kotlin.jvm.functions.Function1<? super T,kotlin.Unit>? onStateChangeFinished = null, kotlin.jvm.functions.Function1<? super androidx.animation.TransitionState,kotlin.Unit> children); method public static boolean getTransitionsEnabled(); method public static void setTransitionsEnabled(boolean p); } diff --git a/ui/ui-animation/api/restricted_0.1.0-dev03.txt b/ui/ui-animation/api/restricted_0.1.0-dev03.txt index 113f0be5a55..e1311a33597 100644 --- a/ui/ui-animation/api/restricted_0.1.0-dev03.txt +++ b/ui/ui-animation/api/restricted_0.1.0-dev03.txt @@ -35,7 +35,7 @@ package androidx.ui.animation { public final class TransitionKt { ctor public TransitionKt(); - method public static <T> void Transition(androidx.animation.TransitionDefinition<T> definition, T? toState, androidx.animation.AnimationClockObservable clock = +ambient(AnimationClockAmbient), kotlin.jvm.functions.Function1<? super androidx.animation.TransitionState,kotlin.Unit> children); + method public static <T> void Transition(androidx.animation.TransitionDefinition<T> definition, T? toState, androidx.animation.AnimationClockObservable clock = +ambient(AnimationClockAmbient), kotlin.jvm.functions.Function1<? super T,kotlin.Unit>? onStateChangeFinished = null, kotlin.jvm.functions.Function1<? super androidx.animation.TransitionState,kotlin.Unit> children); method public static boolean getTransitionsEnabled(); method public static void setTransitionsEnabled(boolean p); } diff --git a/ui/ui-animation/api/restricted_current.txt b/ui/ui-animation/api/restricted_current.txt index 113f0be5a55..e1311a33597 100644 --- a/ui/ui-animation/api/restricted_current.txt +++ b/ui/ui-animation/api/restricted_current.txt @@ -35,7 +35,7 @@ package androidx.ui.animation { public final class TransitionKt { ctor public TransitionKt(); - method public static <T> void Transition(androidx.animation.TransitionDefinition<T> definition, T? toState, androidx.animation.AnimationClockObservable clock = +ambient(AnimationClockAmbient), kotlin.jvm.functions.Function1<? super androidx.animation.TransitionState,kotlin.Unit> children); + method public static <T> void Transition(androidx.animation.TransitionDefinition<T> definition, T? toState, androidx.animation.AnimationClockObservable clock = +ambient(AnimationClockAmbient), kotlin.jvm.functions.Function1<? super T,kotlin.Unit>? onStateChangeFinished = null, kotlin.jvm.functions.Function1<? super androidx.animation.TransitionState,kotlin.Unit> children); method public static boolean getTransitionsEnabled(); method public static void setTransitionsEnabled(boolean p); } diff --git a/ui/ui-animation/build.gradle b/ui/ui-animation/build.gradle index c5fe45b7c25..4808cff9561 100644 --- a/ui/ui-animation/build.gradle +++ b/ui/ui-animation/build.gradle @@ -29,7 +29,7 @@ plugins { } dependencies { - kotlinPlugin project(path: ":compose:compose-compiler", configuration: "embeddablePlugin") + kotlinPlugin project(path: ":compose:compose-compiler") implementation(KOTLIN_STDLIB) diff --git a/ui/ui-animation/integration-tests/animation-demos/build.gradle b/ui/ui-animation/integration-tests/animation-demos/build.gradle index 9530e43ef6e..9433acf576a 100644 --- a/ui/ui-animation/integration-tests/animation-demos/build.gradle +++ b/ui/ui-animation/integration-tests/animation-demos/build.gradle @@ -13,7 +13,7 @@ plugins { } dependencies { - kotlinPlugin project(path: ":compose:compose-compiler", configuration: "embeddablePlugin") + kotlinPlugin project(path: ":compose:compose-compiler") implementation(KOTLIN_STDLIB) diff --git a/ui/ui-animation/integration-tests/animation-demos/src/main/java/androidx/ui/animation/demos/HelloAnimationActivity.kt b/ui/ui-animation/integration-tests/animation-demos/src/main/java/androidx/ui/animation/demos/HelloAnimationActivity.kt index 494f1184923..0b3607bf055 100644 --- a/ui/ui-animation/integration-tests/animation-demos/src/main/java/androidx/ui/animation/demos/HelloAnimationActivity.kt +++ b/ui/ui-animation/integration-tests/animation-demos/src/main/java/androidx/ui/animation/demos/HelloAnimationActivity.kt @@ -67,6 +67,16 @@ private val definition = transitionDefinition { this[background] = Color(red = 188, green = 222, blue = 145, alpha = 255) this[y] = 0f // percentage } + // Apply this transition to all state changes (i.e. Open -> Closed and Closed -> Open) + transition { + background using tween { + duration = 800 + } + y using physics { + // Extremely low stiffness + stiffness = 40f + } + } } val handler = Handler(Looper.getMainLooper()) diff --git a/ui/ui-animation/integration-tests/animation-demos/src/main/java/androidx/ui/animation/demos/HelloGestureBasedAnimationActivity.kt b/ui/ui-animation/integration-tests/animation-demos/src/main/java/androidx/ui/animation/demos/HelloGestureBasedAnimationActivity.kt index 23af1aaa4c7..b79eb01e390 100644 --- a/ui/ui-animation/integration-tests/animation-demos/src/main/java/androidx/ui/animation/demos/HelloGestureBasedAnimationActivity.kt +++ b/ui/ui-animation/integration-tests/animation-demos/src/main/java/androidx/ui/animation/demos/HelloGestureBasedAnimationActivity.kt @@ -60,6 +60,14 @@ private val definition = transitionDefinition { this[scale] = 3f this[color] = Color(red = 0, green = 100, blue = 0, alpha = 255) } + transition { + scale using physics { + stiffness = 50f + } + color using physics { + stiffness = 50f + } + } } @Composable diff --git a/ui/ui-animation/integration-tests/samples/build.gradle b/ui/ui-animation/integration-tests/samples/build.gradle index 5a479b79915..f51e1948d8c 100644 --- a/ui/ui-animation/integration-tests/samples/build.gradle +++ b/ui/ui-animation/integration-tests/samples/build.gradle @@ -26,7 +26,7 @@ plugins { } dependencies { - kotlinPlugin project(path: ":compose:compose-compiler", configuration: "embeddablePlugin") + kotlinPlugin project(path: ":compose:compose-compiler") implementation(KOTLIN_STDLIB) diff --git a/ui/ui-animation/src/main/java/androidx/ui/animation/Transition.kt b/ui/ui-animation/src/main/java/androidx/ui/animation/Transition.kt index 24cc90d8549..fe609ff7fa9 100644 --- a/ui/ui-animation/src/main/java/androidx/ui/animation/Transition.kt +++ b/ui/ui-animation/src/main/java/androidx/ui/animation/Transition.kt @@ -21,6 +21,7 @@ import androidx.animation.PropKey import androidx.animation.TransitionAnimation import androidx.animation.TransitionDefinition import androidx.animation.TransitionState +import androidx.animation.createAnimation import androidx.compose.Composable import androidx.compose.Model import androidx.compose.ambient @@ -38,11 +39,13 @@ fun <T> Transition( definition: TransitionDefinition<T>, toState: T, clock: AnimationClockObservable = +ambient(AnimationClockAmbient), + onStateChangeFinished: ((T) -> Unit)? = null, children: @Composable() (state: TransitionState) -> Unit ) { if (transitionsEnabled) { // TODO: This null is workaround for b/132148894 - val model = +memo(definition, null) { TransitionModel(definition, clock) } + val model = +memo(definition, null) { TransitionModel(definition, toState, clock) } + model.anim.onStateChangeFinished = onStateChangeFinished model.anim.toState(toState) children(model) } else { @@ -61,12 +64,13 @@ var transitionsEnabled = true @Model private class TransitionModel<T>( transitionDef: TransitionDefinition<T>, + initState: T, clock: AnimationClockObservable ) : TransitionState { private var animationPulse = 0L internal val anim: TransitionAnimation<T> = - transitionDef.createAnimation(clock).apply { + transitionDef.createAnimation(clock, initState).apply { onUpdate = { animationPulse++ } diff --git a/ui/ui-core/api/0.1.0-dev03.txt b/ui/ui-core/api/0.1.0-dev03.txt index ba2d75d8d08..20c581b10f4 100644 --- a/ui/ui-core/api/0.1.0-dev03.txt +++ b/ui/ui-core/api/0.1.0-dev03.txt @@ -1268,6 +1268,29 @@ package androidx.ui.engine.geometry { } +package androidx.ui.focus { + + public enum FocusDetailedState { + enum_constant public static final androidx.ui.focus.FocusDetailedState Active; + enum_constant public static final androidx.ui.focus.FocusDetailedState ActiveParent; + enum_constant public static final androidx.ui.focus.FocusDetailedState Captured; + enum_constant public static final androidx.ui.focus.FocusDetailedState Disabled; + enum_constant public static final androidx.ui.focus.FocusDetailedState Inactive; + } + + public enum FocusState { + enum_constant public static final androidx.ui.focus.FocusState Focused; + enum_constant public static final androidx.ui.focus.FocusState NotFocusable; + enum_constant public static final androidx.ui.focus.FocusState NotFocused; + } + + public final class FocusStateKt { + ctor public FocusStateKt(); + method public static androidx.ui.focus.FocusState focusState(androidx.ui.focus.FocusDetailedState); + } + +} + package androidx.ui.graphics { public final class AndroidCanvasKt { diff --git a/ui/ui-core/api/current.txt b/ui/ui-core/api/current.txt index ba2d75d8d08..20c581b10f4 100644 --- a/ui/ui-core/api/current.txt +++ b/ui/ui-core/api/current.txt @@ -1268,6 +1268,29 @@ package androidx.ui.engine.geometry { } +package androidx.ui.focus { + + public enum FocusDetailedState { + enum_constant public static final androidx.ui.focus.FocusDetailedState Active; + enum_constant public static final androidx.ui.focus.FocusDetailedState ActiveParent; + enum_constant public static final androidx.ui.focus.FocusDetailedState Captured; + enum_constant public static final androidx.ui.focus.FocusDetailedState Disabled; + enum_constant public static final androidx.ui.focus.FocusDetailedState Inactive; + } + + public enum FocusState { + enum_constant public static final androidx.ui.focus.FocusState Focused; + enum_constant public static final androidx.ui.focus.FocusState NotFocusable; + enum_constant public static final androidx.ui.focus.FocusState NotFocused; + } + + public final class FocusStateKt { + ctor public FocusStateKt(); + method public static androidx.ui.focus.FocusState focusState(androidx.ui.focus.FocusDetailedState); + } + +} + package androidx.ui.graphics { public final class AndroidCanvasKt { diff --git a/ui/ui-core/api/public_plus_experimental_0.1.0-dev03.txt b/ui/ui-core/api/public_plus_experimental_0.1.0-dev03.txt index ba2d75d8d08..20c581b10f4 100644 --- a/ui/ui-core/api/public_plus_experimental_0.1.0-dev03.txt +++ b/ui/ui-core/api/public_plus_experimental_0.1.0-dev03.txt @@ -1268,6 +1268,29 @@ package androidx.ui.engine.geometry { } +package androidx.ui.focus { + + public enum FocusDetailedState { + enum_constant public static final androidx.ui.focus.FocusDetailedState Active; + enum_constant public static final androidx.ui.focus.FocusDetailedState ActiveParent; + enum_constant public static final androidx.ui.focus.FocusDetailedState Captured; + enum_constant public static final androidx.ui.focus.FocusDetailedState Disabled; + enum_constant public static final androidx.ui.focus.FocusDetailedState Inactive; + } + + public enum FocusState { + enum_constant public static final androidx.ui.focus.FocusState Focused; + enum_constant public static final androidx.ui.focus.FocusState NotFocusable; + enum_constant public static final androidx.ui.focus.FocusState NotFocused; + } + + public final class FocusStateKt { + ctor public FocusStateKt(); + method public static androidx.ui.focus.FocusState focusState(androidx.ui.focus.FocusDetailedState); + } + +} + package androidx.ui.graphics { public final class AndroidCanvasKt { diff --git a/ui/ui-core/api/public_plus_experimental_current.txt b/ui/ui-core/api/public_plus_experimental_current.txt index ba2d75d8d08..20c581b10f4 100644 --- a/ui/ui-core/api/public_plus_experimental_current.txt +++ b/ui/ui-core/api/public_plus_experimental_current.txt @@ -1268,6 +1268,29 @@ package androidx.ui.engine.geometry { } +package androidx.ui.focus { + + public enum FocusDetailedState { + enum_constant public static final androidx.ui.focus.FocusDetailedState Active; + enum_constant public static final androidx.ui.focus.FocusDetailedState ActiveParent; + enum_constant public static final androidx.ui.focus.FocusDetailedState Captured; + enum_constant public static final androidx.ui.focus.FocusDetailedState Disabled; + enum_constant public static final androidx.ui.focus.FocusDetailedState Inactive; + } + + public enum FocusState { + enum_constant public static final androidx.ui.focus.FocusState Focused; + enum_constant public static final androidx.ui.focus.FocusState NotFocusable; + enum_constant public static final androidx.ui.focus.FocusState NotFocused; + } + + public final class FocusStateKt { + ctor public FocusStateKt(); + method public static androidx.ui.focus.FocusState focusState(androidx.ui.focus.FocusDetailedState); + } + +} + package androidx.ui.graphics { public final class AndroidCanvasKt { diff --git a/ui/ui-core/api/restricted_0.1.0-dev03.txt b/ui/ui-core/api/restricted_0.1.0-dev03.txt index ba2d75d8d08..20c581b10f4 100644 --- a/ui/ui-core/api/restricted_0.1.0-dev03.txt +++ b/ui/ui-core/api/restricted_0.1.0-dev03.txt @@ -1268,6 +1268,29 @@ package androidx.ui.engine.geometry { } +package androidx.ui.focus { + + public enum FocusDetailedState { + enum_constant public static final androidx.ui.focus.FocusDetailedState Active; + enum_constant public static final androidx.ui.focus.FocusDetailedState ActiveParent; + enum_constant public static final androidx.ui.focus.FocusDetailedState Captured; + enum_constant public static final androidx.ui.focus.FocusDetailedState Disabled; + enum_constant public static final androidx.ui.focus.FocusDetailedState Inactive; + } + + public enum FocusState { + enum_constant public static final androidx.ui.focus.FocusState Focused; + enum_constant public static final androidx.ui.focus.FocusState NotFocusable; + enum_constant public static final androidx.ui.focus.FocusState NotFocused; + } + + public final class FocusStateKt { + ctor public FocusStateKt(); + method public static androidx.ui.focus.FocusState focusState(androidx.ui.focus.FocusDetailedState); + } + +} + package androidx.ui.graphics { public final class AndroidCanvasKt { diff --git a/ui/ui-core/api/restricted_current.txt b/ui/ui-core/api/restricted_current.txt index ba2d75d8d08..20c581b10f4 100644 --- a/ui/ui-core/api/restricted_current.txt +++ b/ui/ui-core/api/restricted_current.txt @@ -1268,6 +1268,29 @@ package androidx.ui.engine.geometry { } +package androidx.ui.focus { + + public enum FocusDetailedState { + enum_constant public static final androidx.ui.focus.FocusDetailedState Active; + enum_constant public static final androidx.ui.focus.FocusDetailedState ActiveParent; + enum_constant public static final androidx.ui.focus.FocusDetailedState Captured; + enum_constant public static final androidx.ui.focus.FocusDetailedState Disabled; + enum_constant public static final androidx.ui.focus.FocusDetailedState Inactive; + } + + public enum FocusState { + enum_constant public static final androidx.ui.focus.FocusState Focused; + enum_constant public static final androidx.ui.focus.FocusState NotFocusable; + enum_constant public static final androidx.ui.focus.FocusState NotFocused; + } + + public final class FocusStateKt { + ctor public FocusStateKt(); + method public static androidx.ui.focus.FocusState focusState(androidx.ui.focus.FocusDetailedState); + } + +} + package androidx.ui.graphics { public final class AndroidCanvasKt { diff --git a/ui/ui-core/integration-tests/samples/build.gradle b/ui/ui-core/integration-tests/samples/build.gradle index d8814643426..84ad5db2a52 100644 --- a/ui/ui-core/integration-tests/samples/build.gradle +++ b/ui/ui-core/integration-tests/samples/build.gradle @@ -26,7 +26,7 @@ plugins { } dependencies { - kotlinPlugin project(path: ":compose:compose-compiler", configuration: "embeddablePlugin") + kotlinPlugin project(path: ":compose:compose-compiler") implementation(KOTLIN_STDLIB) diff --git a/ui/ui-core/src/main/java/androidx/ui/focus/FocusState.kt b/ui/ui-core/src/main/java/androidx/ui/focus/FocusState.kt new file mode 100644 index 00000000000..e2cae0ca6a6 --- /dev/null +++ b/ui/ui-core/src/main/java/androidx/ui/focus/FocusState.kt @@ -0,0 +1,56 @@ +/* + * Copyright 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package androidx.ui.focus + +/** + * Different states of the focus system. + * + * These are the most frequently used states. For more detailed states, refer to + * [FocusDetailedState]. + * + * [Focused]: A focusable component that is currently in focus. + * [NotFocusable]: A focusable component that is currently not focusable. Eg. A disabled button. + * [NotFocused]: A focusable component that is not currently focused. + */ +enum class FocusState { Focused, NotFocusable, NotFocused } + +/** + * Different states of the focus system. + * These are the detailed states used by the Focus Nodes. + * If you need higher level states, eg [Focused][FocusState.Focused] or + * [NotFocused][FocusState.NotFocused], use the states in [FocusState]. + * + * [Active]: The focusable component is currently active (i.e. it receives key events). + * [ActiveParent] : One of the descendants of the focusable component is [Active]. + * [Captured]: The focusable component is currently active (has focus), and is in a state where + * it does not want to give up focus. (Eg. a text field with an invalid phone number). + * [Disabled]: The focusable component is not currently focusable. (eg. A disabled button). + * [Inactive]: The focusable component does not receive any key events. (ie it is not active, + * nor are any of its descendants active). + */ +enum class FocusDetailedState { Active, ActiveParent, Captured, Disabled, Inactive } + +/** + * Converts a [FocusDetailedState] to a [FocusState]. + */ +fun FocusDetailedState.focusState() = when (this) { + FocusDetailedState.Captured, + FocusDetailedState.Active -> FocusState.Focused + FocusDetailedState.ActiveParent, + FocusDetailedState.Inactive -> FocusState.NotFocused + FocusDetailedState.Disabled -> FocusState.NotFocusable +} diff --git a/ui/ui-foundation/build.gradle b/ui/ui-foundation/build.gradle index 8cd68d14a43..c5a01718928 100644 --- a/ui/ui-foundation/build.gradle +++ b/ui/ui-foundation/build.gradle @@ -29,7 +29,7 @@ plugins { } dependencies { - kotlinPlugin project(path: ":compose:compose-compiler", configuration: "embeddablePlugin") + kotlinPlugin project(path: ":compose:compose-compiler") implementation(KOTLIN_STDLIB) diff --git a/ui/ui-foundation/integration-tests/foundation-demos/build.gradle b/ui/ui-foundation/integration-tests/foundation-demos/build.gradle index 5c913868ed2..9ea749979df 100644 --- a/ui/ui-foundation/integration-tests/foundation-demos/build.gradle +++ b/ui/ui-foundation/integration-tests/foundation-demos/build.gradle @@ -29,7 +29,7 @@ plugins { } dependencies { - kotlinPlugin project(path: ":compose:compose-compiler", configuration: "embeddablePlugin") + kotlinPlugin project(path: ":compose:compose-compiler") implementation(KOTLIN_COROUTINES_ANDROID) implementation(KOTLIN_STDLIB) diff --git a/ui/ui-foundation/integration-tests/samples/build.gradle b/ui/ui-foundation/integration-tests/samples/build.gradle index ba7a9a4d14f..d6a2247ffa9 100644 --- a/ui/ui-foundation/integration-tests/samples/build.gradle +++ b/ui/ui-foundation/integration-tests/samples/build.gradle @@ -26,7 +26,7 @@ plugins { } dependencies { - kotlinPlugin project(path: ":compose:compose-compiler", configuration: "embeddablePlugin") + kotlinPlugin project(path: ":compose:compose-compiler") implementation(KOTLIN_STDLIB) diff --git a/ui/ui-framework/api/0.1.0-dev03.txt b/ui/ui-framework/api/0.1.0-dev03.txt index 3344b1c600d..521cfa16686 100644 --- a/ui/ui-framework/api/0.1.0-dev03.txt +++ b/ui/ui-framework/api/0.1.0-dev03.txt @@ -350,6 +350,26 @@ package androidx.ui.core.selection { } +package androidx.ui.focus { + + public final class FocusOperator { + ctor public FocusOperator(); + method public androidx.ui.focus.FocusDetailedState getFocusDetailedState(); + method public androidx.ui.focus.FocusState getFocusState(); + method public void requestFocus(); + property public final androidx.ui.focus.FocusDetailedState focusDetailedState; + property public final androidx.ui.focus.FocusState focusState; + } + + public final class FocusableKt { + ctor public FocusableKt(); + method public static void Focusable(androidx.ui.focus.FocusOperator focusOperator = +memo({ + <init>() +}), kotlin.jvm.functions.Function1<? super androidx.ui.focus.FocusOperator,kotlin.Unit> children); + } + +} + package androidx.ui.graphics.vector { public final class VectorAsset { diff --git a/ui/ui-framework/api/current.txt b/ui/ui-framework/api/current.txt index 3344b1c600d..521cfa16686 100644 --- a/ui/ui-framework/api/current.txt +++ b/ui/ui-framework/api/current.txt @@ -350,6 +350,26 @@ package androidx.ui.core.selection { } +package androidx.ui.focus { + + public final class FocusOperator { + ctor public FocusOperator(); + method public androidx.ui.focus.FocusDetailedState getFocusDetailedState(); + method public androidx.ui.focus.FocusState getFocusState(); + method public void requestFocus(); + property public final androidx.ui.focus.FocusDetailedState focusDetailedState; + property public final androidx.ui.focus.FocusState focusState; + } + + public final class FocusableKt { + ctor public FocusableKt(); + method public static void Focusable(androidx.ui.focus.FocusOperator focusOperator = +memo({ + <init>() +}), kotlin.jvm.functions.Function1<? super androidx.ui.focus.FocusOperator,kotlin.Unit> children); + } + +} + package androidx.ui.graphics.vector { public final class VectorAsset { diff --git a/ui/ui-framework/api/public_plus_experimental_0.1.0-dev03.txt b/ui/ui-framework/api/public_plus_experimental_0.1.0-dev03.txt index 3344b1c600d..521cfa16686 100644 --- a/ui/ui-framework/api/public_plus_experimental_0.1.0-dev03.txt +++ b/ui/ui-framework/api/public_plus_experimental_0.1.0-dev03.txt @@ -350,6 +350,26 @@ package androidx.ui.core.selection { } +package androidx.ui.focus { + + public final class FocusOperator { + ctor public FocusOperator(); + method public androidx.ui.focus.FocusDetailedState getFocusDetailedState(); + method public androidx.ui.focus.FocusState getFocusState(); + method public void requestFocus(); + property public final androidx.ui.focus.FocusDetailedState focusDetailedState; + property public final androidx.ui.focus.FocusState focusState; + } + + public final class FocusableKt { + ctor public FocusableKt(); + method public static void Focusable(androidx.ui.focus.FocusOperator focusOperator = +memo({ + <init>() +}), kotlin.jvm.functions.Function1<? super androidx.ui.focus.FocusOperator,kotlin.Unit> children); + } + +} + package androidx.ui.graphics.vector { public final class VectorAsset { diff --git a/ui/ui-framework/api/public_plus_experimental_current.txt b/ui/ui-framework/api/public_plus_experimental_current.txt index 3344b1c600d..521cfa16686 100644 --- a/ui/ui-framework/api/public_plus_experimental_current.txt +++ b/ui/ui-framework/api/public_plus_experimental_current.txt @@ -350,6 +350,26 @@ package androidx.ui.core.selection { } +package androidx.ui.focus { + + public final class FocusOperator { + ctor public FocusOperator(); + method public androidx.ui.focus.FocusDetailedState getFocusDetailedState(); + method public androidx.ui.focus.FocusState getFocusState(); + method public void requestFocus(); + property public final androidx.ui.focus.FocusDetailedState focusDetailedState; + property public final androidx.ui.focus.FocusState focusState; + } + + public final class FocusableKt { + ctor public FocusableKt(); + method public static void Focusable(androidx.ui.focus.FocusOperator focusOperator = +memo({ + <init>() +}), kotlin.jvm.functions.Function1<? super androidx.ui.focus.FocusOperator,kotlin.Unit> children); + } + +} + package androidx.ui.graphics.vector { public final class VectorAsset { diff --git a/ui/ui-framework/api/restricted_0.1.0-dev03.txt b/ui/ui-framework/api/restricted_0.1.0-dev03.txt index 3344b1c600d..521cfa16686 100644 --- a/ui/ui-framework/api/restricted_0.1.0-dev03.txt +++ b/ui/ui-framework/api/restricted_0.1.0-dev03.txt @@ -350,6 +350,26 @@ package androidx.ui.core.selection { } +package androidx.ui.focus { + + public final class FocusOperator { + ctor public FocusOperator(); + method public androidx.ui.focus.FocusDetailedState getFocusDetailedState(); + method public androidx.ui.focus.FocusState getFocusState(); + method public void requestFocus(); + property public final androidx.ui.focus.FocusDetailedState focusDetailedState; + property public final androidx.ui.focus.FocusState focusState; + } + + public final class FocusableKt { + ctor public FocusableKt(); + method public static void Focusable(androidx.ui.focus.FocusOperator focusOperator = +memo({ + <init>() +}), kotlin.jvm.functions.Function1<? super androidx.ui.focus.FocusOperator,kotlin.Unit> children); + } + +} + package androidx.ui.graphics.vector { public final class VectorAsset { diff --git a/ui/ui-framework/api/restricted_current.txt b/ui/ui-framework/api/restricted_current.txt index 3344b1c600d..521cfa16686 100644 --- a/ui/ui-framework/api/restricted_current.txt +++ b/ui/ui-framework/api/restricted_current.txt @@ -350,6 +350,26 @@ package androidx.ui.core.selection { } +package androidx.ui.focus { + + public final class FocusOperator { + ctor public FocusOperator(); + method public androidx.ui.focus.FocusDetailedState getFocusDetailedState(); + method public androidx.ui.focus.FocusState getFocusState(); + method public void requestFocus(); + property public final androidx.ui.focus.FocusDetailedState focusDetailedState; + property public final androidx.ui.focus.FocusState focusState; + } + + public final class FocusableKt { + ctor public FocusableKt(); + method public static void Focusable(androidx.ui.focus.FocusOperator focusOperator = +memo({ + <init>() +}), kotlin.jvm.functions.Function1<? super androidx.ui.focus.FocusOperator,kotlin.Unit> children); + } + +} + package androidx.ui.graphics.vector { public final class VectorAsset { diff --git a/ui/ui-framework/build.gradle b/ui/ui-framework/build.gradle index 7874e613a03..f956d84173e 100644 --- a/ui/ui-framework/build.gradle +++ b/ui/ui-framework/build.gradle @@ -29,7 +29,7 @@ plugins { } dependencies { - kotlinPlugin project(path: ":compose:compose-compiler", configuration: "embeddablePlugin") + kotlinPlugin project(path: ":compose:compose-compiler") implementation(KOTLIN_COROUTINES_ANDROID) implementation(KOTLIN_STDLIB) diff --git a/ui/ui-framework/integration-tests/framework-demos/build.gradle b/ui/ui-framework/integration-tests/framework-demos/build.gradle index 6e950b9fe2a..2aa44307434 100644 --- a/ui/ui-framework/integration-tests/framework-demos/build.gradle +++ b/ui/ui-framework/integration-tests/framework-demos/build.gradle @@ -12,7 +12,7 @@ plugins { } dependencies { - kotlinPlugin project(path: ":compose:compose-compiler", configuration: "embeddablePlugin") + kotlinPlugin project(path: ":compose:compose-compiler") implementation(KOTLIN_STDLIB) diff --git a/ui/ui-framework/integration-tests/framework-demos/src/main/AndroidManifest.xml b/ui/ui-framework/integration-tests/framework-demos/src/main/AndroidManifest.xml index 178834d4030..e962fd8dfa5 100644 --- a/ui/ui-framework/integration-tests/framework-demos/src/main/AndroidManifest.xml +++ b/ui/ui-framework/integration-tests/framework-demos/src/main/AndroidManifest.xml @@ -77,7 +77,14 @@ <category android:name="androidx.ui.demos.SAMPLE_CODE" /> </intent-filter> </activity> - + <activity android:name=".focus.FocusableActivity" + android:configChanges="orientation|screenSize" + android:label="Focus"> + <intent-filter> + <action android:name="android.intent.action.MAIN"/> + <category android:name="androidx.ui.demos.SAMPLE_CODE"/> + </intent-filter> + </activity> <!-- Simple Movement Based GestureDetector Demos --> <activity android:name=".gestures.TouchSlopDragGestureDetectorDemo" android:configChanges="orientation|screenSize" diff --git a/ui/ui-framework/integration-tests/framework-demos/src/main/java/androidx/ui/framework/demos/focus/FocusableActivity.kt b/ui/ui-framework/integration-tests/framework-demos/src/main/java/androidx/ui/framework/demos/focus/FocusableActivity.kt new file mode 100644 index 00000000000..9dd9b411b40 --- /dev/null +++ b/ui/ui-framework/integration-tests/framework-demos/src/main/java/androidx/ui/framework/demos/focus/FocusableActivity.kt @@ -0,0 +1,85 @@ +/* + * Copyright 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package androidx.ui.framework.demos.focus + +import android.app.Activity +import android.os.Bundle +import androidx.compose.Composable +import androidx.ui.core.Text +import androidx.ui.core.gesture.PressGestureDetector +import androidx.ui.core.setContent +import androidx.ui.focus.FocusState.NotFocused +import androidx.ui.focus.FocusState.NotFocusable +import androidx.ui.focus.FocusState.Focused +import androidx.ui.focus.Focusable +import androidx.ui.graphics.Color +import androidx.ui.layout.Column +import androidx.ui.layout.ExpandedWidth +import androidx.ui.layout.MainAxisAlignment +import androidx.ui.layout.Row +import androidx.ui.layout.RowScope +import androidx.ui.material.MaterialTheme +import androidx.ui.text.TextStyle + +class FocusableActivity : Activity() { + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + setContent { + Focusable { + MaterialTheme { + Column(mainAxisAlignment = MainAxisAlignment.SpaceEvenly) { + CenteredRow { + Text("Click on any focusable to bring it into focus:") + } + CenteredRow { + FocusableText("Focusable 1") + } + CenteredRow { + FocusableText("Focusable 2") + } + CenteredRow { + FocusableText("Focusable 3") + } + } + } + } + } + } +} + +@Composable +private fun FocusableText(text: String) { + Focusable { focus -> + PressGestureDetector(onPress = { focus.requestFocus() }) { + Text( + text = text, + style = TextStyle( + color = when (focus.focusState) { + Focused -> Color.Green + NotFocused -> Color.Black + NotFocusable -> Color.Gray + } + ) + ) + } + } +} + +@Composable +private fun CenteredRow(children: @Composable() RowScope.() -> Unit) { + Row(modifier = ExpandedWidth, mainAxisAlignment = MainAxisAlignment.Center, children = children) +}
\ No newline at end of file diff --git a/ui/ui-framework/integration-tests/samples/build.gradle b/ui/ui-framework/integration-tests/samples/build.gradle index f83e20c840a..e322408b6d5 100644 --- a/ui/ui-framework/integration-tests/samples/build.gradle +++ b/ui/ui-framework/integration-tests/samples/build.gradle @@ -26,7 +26,7 @@ plugins { } dependencies { - kotlinPlugin project(path: ":compose:compose-compiler", configuration: "embeddablePlugin") + kotlinPlugin project(path: ":compose:compose-compiler") implementation(KOTLIN_STDLIB) diff --git a/ui/ui-framework/src/main/java/androidx/ui/focus/Focusable.kt b/ui/ui-framework/src/main/java/androidx/ui/focus/Focusable.kt new file mode 100644 index 00000000000..c1c75510ffb --- /dev/null +++ b/ui/ui-framework/src/main/java/androidx/ui/focus/Focusable.kt @@ -0,0 +1,106 @@ +/* + * Copyright 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package androidx.ui.focus + +import androidx.compose.Composable +import androidx.compose.Recompose +import androidx.compose.memo +import androidx.compose.unaryPlus +import androidx.ui.core.FocusNode +import androidx.ui.core.OnChildPositioned +import androidx.ui.core.Ref +import androidx.ui.core.focus.initializeFocusState +import androidx.ui.focus.FocusDetailedState.Inactive + +private val focusNotCreated = "Focus node could not be created." + +/** + * This composable can be used to create components that are Focusable. A component that is focused + * receives any invoked actions. Some examples of actions are 'paste' (receiving the + * contents of the clipboard), or receiving text from the keyboard. + * + * [Focusable] components have access to the current focus state. The children of a [Focusable] + * have access to this focus state during composition. + * + * [focusOperator] : This object is returned in the receiver scope of the components + * passed as [children]. You should not specify this parameter unless you want to hoist the + * focusOperator so that you can control the focusable from outside the scope of its children. + * + * [children]: This is a composable block called with [focusOperator] in its receiver scope. + * Children can use [FocusOperator.focusState] for conditional composition. + * + */ +@Composable +fun Focusable( + focusOperator: FocusOperator = +memo { FocusOperator() }, + children: @Composable() (FocusOperator) -> Unit +) { + // TODO (b/144897112): Remove manual recomposition. + Recompose { recompose -> + + val focusNodeRef = Ref<FocusNode>() + FocusNode(recompose = recompose, ref = focusNodeRef) { + + val focusNode = (focusNodeRef.value ?: error(focusNotCreated)) + + focusOperator.focusNode = focusNode + + // Set the focusNode coordinates when the composable is positioned. Also, if this is + // the focus root and the host view is in focus, request focus for this node. + OnChildPositioned( + onPositioned = { + focusNode.layoutCoordinates = it + if (focusNode.focusState == Inactive) { + focusNode.initializeFocusState() + } + }, children = { + children(focusOperator) + }) + } + } +} + +/** + * The [FocusOperator] is returned in the receiver scope of the children of a [Focusable]. It + * access to focus APIs pertaining to the [Focusable]. + */ +class FocusOperator { + /** + * The [FocusNode] associated with this [FocusOperator]. + * + * @throws UninitializedPropertyAccessException if this [FocusOperator] has no associated + * [FocusNode]. + */ + internal lateinit var focusNode: FocusNode + + /** + * A more detailed focus state of the [Focusable] associated with this [FocusOperator]. For a + * smaller subset of states, use [focusState]. + */ + val focusDetailedState: FocusDetailedState get() = focusNode.focusState + + /** + * The current focus state of the [Focusable] associated with this [FocusOperator]. For more + * detailed focus state information, use [focusDetailedState]. + */ + val focusState: FocusState get() = focusDetailedState.focusState() + + /** + * Request focus for the [Focusable] associated with this [FocusOperator]. + */ + fun requestFocus() = focusNode.requestFocus() +}
\ No newline at end of file diff --git a/ui/ui-layout/build.gradle b/ui/ui-layout/build.gradle index 6805851fa58..9beabcf072b 100644 --- a/ui/ui-layout/build.gradle +++ b/ui/ui-layout/build.gradle @@ -30,7 +30,7 @@ plugins { } dependencies { - kotlinPlugin project(path: ":compose:compose-compiler", configuration: "embeddablePlugin") + kotlinPlugin project(path: ":compose:compose-compiler") implementation(KOTLIN_STDLIB) diff --git a/ui/ui-layout/integration-tests/layout-demos/build.gradle b/ui/ui-layout/integration-tests/layout-demos/build.gradle index de2f8818e55..7fff9e3489b 100644 --- a/ui/ui-layout/integration-tests/layout-demos/build.gradle +++ b/ui/ui-layout/integration-tests/layout-demos/build.gradle @@ -27,7 +27,7 @@ plugins { } dependencies { - kotlinPlugin project(path: ":compose:compose-compiler", configuration: "embeddablePlugin") + kotlinPlugin project(path: ":compose:compose-compiler") implementation(KOTLIN_COROUTINES_ANDROID) implementation(KOTLIN_STDLIB) diff --git a/ui/ui-layout/integration-tests/samples/build.gradle b/ui/ui-layout/integration-tests/samples/build.gradle index 2f85730d21f..c9bab7aba58 100644 --- a/ui/ui-layout/integration-tests/samples/build.gradle +++ b/ui/ui-layout/integration-tests/samples/build.gradle @@ -26,7 +26,7 @@ plugins { } dependencies { - kotlinPlugin project(path: ":compose:compose-compiler", configuration: "embeddablePlugin") + kotlinPlugin project(path: ":compose:compose-compiler") implementation(KOTLIN_STDLIB) diff --git a/ui/ui-material/build.gradle b/ui/ui-material/build.gradle index 0ceb2c18478..0883fe8359a 100644 --- a/ui/ui-material/build.gradle +++ b/ui/ui-material/build.gradle @@ -29,7 +29,7 @@ plugins { } dependencies { - kotlinPlugin project(path: ":compose:compose-compiler", configuration: "embeddablePlugin") + kotlinPlugin project(path: ":compose:compose-compiler") implementation(KOTLIN_STDLIB) diff --git a/ui/ui-material/integration-tests/material-demos/build.gradle b/ui/ui-material/integration-tests/material-demos/build.gradle index 674c142932b..35fea8bd31b 100644 --- a/ui/ui-material/integration-tests/material-demos/build.gradle +++ b/ui/ui-material/integration-tests/material-demos/build.gradle @@ -13,7 +13,7 @@ plugins { } dependencies { - kotlinPlugin project(path: ":compose:compose-compiler", configuration: "embeddablePlugin") + kotlinPlugin project(path: ":compose:compose-compiler") implementation(KOTLIN_COROUTINES_ANDROID) implementation(KOTLIN_REFLECT) diff --git a/ui/ui-material/integration-tests/material-studies/build.gradle b/ui/ui-material/integration-tests/material-studies/build.gradle index 56efbbf9911..7667548ad47 100644 --- a/ui/ui-material/integration-tests/material-studies/build.gradle +++ b/ui/ui-material/integration-tests/material-studies/build.gradle @@ -27,7 +27,7 @@ plugins { } dependencies { - kotlinPlugin project(path: ":compose:compose-compiler", configuration: "embeddablePlugin") + kotlinPlugin project(path: ":compose:compose-compiler") implementation(KOTLIN_STDLIB) diff --git a/ui/ui-material/integration-tests/samples/build.gradle b/ui/ui-material/integration-tests/samples/build.gradle index 3762ffd427d..0cef6f5dbb3 100644 --- a/ui/ui-material/integration-tests/samples/build.gradle +++ b/ui/ui-material/integration-tests/samples/build.gradle @@ -26,7 +26,7 @@ plugins { } dependencies { - kotlinPlugin project(path: ":compose:compose-compiler", configuration: "embeddablePlugin") + kotlinPlugin project(path: ":compose:compose-compiler") implementation(KOTLIN_STDLIB) diff --git a/ui/ui-material/src/main/java/androidx/ui/material/ripple/DefaultRippleEffect.kt b/ui/ui-material/src/main/java/androidx/ui/material/ripple/DefaultRippleEffect.kt index d93a99da3c2..9a766e68bd5 100644 --- a/ui/ui-material/src/main/java/androidx/ui/material/ripple/DefaultRippleEffect.kt +++ b/ui/ui-material/src/main/java/androidx/ui/material/ripple/DefaultRippleEffect.kt @@ -21,6 +21,7 @@ import androidx.animation.FloatPropKey import androidx.animation.InterruptionHandling import androidx.animation.LinearEasing import androidx.animation.TransitionAnimation +import androidx.animation.createAnimation import androidx.animation.transitionDefinition import androidx.ui.animation.PxPositionPropKey import androidx.ui.animation.PxPropKey diff --git a/ui/ui-platform/api/0.1.0-dev03.txt b/ui/ui-platform/api/0.1.0-dev03.txt index 7cdc7590cea..f288fffcdb4 100644 --- a/ui/ui-platform/api/0.1.0-dev03.txt +++ b/ui/ui-platform/api/0.1.0-dev03.txt @@ -140,6 +140,25 @@ package androidx.ui.core { property public final kotlin.jvm.functions.Function3<androidx.ui.core.DrawReceiver,androidx.ui.graphics.Canvas,androidx.ui.core.PxSize,kotlin.Unit>? onPaintWithChildren; } + public final class FocusNode extends androidx.ui.core.ComponentNode { + ctor public FocusNode(); + method public boolean captureFocus(); + method public boolean freeFocus(); + method public androidx.ui.focus.FocusDetailedState getFocusState(); + method public androidx.ui.core.LayoutCoordinates? getLayoutCoordinates(); + method public kotlin.jvm.functions.Function0<kotlin.Unit> getRecompose(); + method public androidx.ui.core.Ref<androidx.ui.core.FocusNode>? getRef(); + method public void requestFocus(boolean propagateFocus = true); + method public void setFocusState$lintWithKotlin(androidx.ui.focus.FocusDetailedState p); + method public void setLayoutCoordinates(androidx.ui.core.LayoutCoordinates? p); + method public void setRecompose(kotlin.jvm.functions.Function0<kotlin.Unit> value); + method public void setRef(androidx.ui.core.Ref<androidx.ui.core.FocusNode>? value); + property public final androidx.ui.focus.FocusDetailedState focusState; + property public final androidx.ui.core.LayoutCoordinates? layoutCoordinates; + property public final kotlin.jvm.functions.Function0<kotlin.Unit> recompose; + property public final androidx.ui.core.Ref<androidx.ui.core.FocusNode>? ref; + } + public final class LayoutNode extends androidx.ui.core.ComponentNode implements androidx.ui.core.Measurable { ctor public LayoutNode(); method public boolean getAffectsParentSize(); @@ -324,6 +343,15 @@ package androidx.ui.core { } +package androidx.ui.core.focus { + + public final class FocusNodeUtilsKt { + ctor public FocusNodeUtilsKt(); + method public static void initializeFocusState(androidx.ui.core.FocusNode); + } + +} + package androidx.ui.core.pointerinput { public final class MotionEventAdapterKt { diff --git a/ui/ui-platform/api/current.txt b/ui/ui-platform/api/current.txt index 7cdc7590cea..f288fffcdb4 100644 --- a/ui/ui-platform/api/current.txt +++ b/ui/ui-platform/api/current.txt @@ -140,6 +140,25 @@ package androidx.ui.core { property public final kotlin.jvm.functions.Function3<androidx.ui.core.DrawReceiver,androidx.ui.graphics.Canvas,androidx.ui.core.PxSize,kotlin.Unit>? onPaintWithChildren; } + public final class FocusNode extends androidx.ui.core.ComponentNode { + ctor public FocusNode(); + method public boolean captureFocus(); + method public boolean freeFocus(); + method public androidx.ui.focus.FocusDetailedState getFocusState(); + method public androidx.ui.core.LayoutCoordinates? getLayoutCoordinates(); + method public kotlin.jvm.functions.Function0<kotlin.Unit> getRecompose(); + method public androidx.ui.core.Ref<androidx.ui.core.FocusNode>? getRef(); + method public void requestFocus(boolean propagateFocus = true); + method public void setFocusState$lintWithKotlin(androidx.ui.focus.FocusDetailedState p); + method public void setLayoutCoordinates(androidx.ui.core.LayoutCoordinates? p); + method public void setRecompose(kotlin.jvm.functions.Function0<kotlin.Unit> value); + method public void setRef(androidx.ui.core.Ref<androidx.ui.core.FocusNode>? value); + property public final androidx.ui.focus.FocusDetailedState focusState; + property public final androidx.ui.core.LayoutCoordinates? layoutCoordinates; + property public final kotlin.jvm.functions.Function0<kotlin.Unit> recompose; + property public final androidx.ui.core.Ref<androidx.ui.core.FocusNode>? ref; + } + public final class LayoutNode extends androidx.ui.core.ComponentNode implements androidx.ui.core.Measurable { ctor public LayoutNode(); method public boolean getAffectsParentSize(); @@ -324,6 +343,15 @@ package androidx.ui.core { } +package androidx.ui.core.focus { + + public final class FocusNodeUtilsKt { + ctor public FocusNodeUtilsKt(); + method public static void initializeFocusState(androidx.ui.core.FocusNode); + } + +} + package androidx.ui.core.pointerinput { public final class MotionEventAdapterKt { diff --git a/ui/ui-platform/api/public_plus_experimental_0.1.0-dev03.txt b/ui/ui-platform/api/public_plus_experimental_0.1.0-dev03.txt index 7cdc7590cea..f288fffcdb4 100644 --- a/ui/ui-platform/api/public_plus_experimental_0.1.0-dev03.txt +++ b/ui/ui-platform/api/public_plus_experimental_0.1.0-dev03.txt @@ -140,6 +140,25 @@ package androidx.ui.core { property public final kotlin.jvm.functions.Function3<androidx.ui.core.DrawReceiver,androidx.ui.graphics.Canvas,androidx.ui.core.PxSize,kotlin.Unit>? onPaintWithChildren; } + public final class FocusNode extends androidx.ui.core.ComponentNode { + ctor public FocusNode(); + method public boolean captureFocus(); + method public boolean freeFocus(); + method public androidx.ui.focus.FocusDetailedState getFocusState(); + method public androidx.ui.core.LayoutCoordinates? getLayoutCoordinates(); + method public kotlin.jvm.functions.Function0<kotlin.Unit> getRecompose(); + method public androidx.ui.core.Ref<androidx.ui.core.FocusNode>? getRef(); + method public void requestFocus(boolean propagateFocus = true); + method public void setFocusState$lintWithKotlin(androidx.ui.focus.FocusDetailedState p); + method public void setLayoutCoordinates(androidx.ui.core.LayoutCoordinates? p); + method public void setRecompose(kotlin.jvm.functions.Function0<kotlin.Unit> value); + method public void setRef(androidx.ui.core.Ref<androidx.ui.core.FocusNode>? value); + property public final androidx.ui.focus.FocusDetailedState focusState; + property public final androidx.ui.core.LayoutCoordinates? layoutCoordinates; + property public final kotlin.jvm.functions.Function0<kotlin.Unit> recompose; + property public final androidx.ui.core.Ref<androidx.ui.core.FocusNode>? ref; + } + public final class LayoutNode extends androidx.ui.core.ComponentNode implements androidx.ui.core.Measurable { ctor public LayoutNode(); method public boolean getAffectsParentSize(); @@ -324,6 +343,15 @@ package androidx.ui.core { } +package androidx.ui.core.focus { + + public final class FocusNodeUtilsKt { + ctor public FocusNodeUtilsKt(); + method public static void initializeFocusState(androidx.ui.core.FocusNode); + } + +} + package androidx.ui.core.pointerinput { public final class MotionEventAdapterKt { diff --git a/ui/ui-platform/api/public_plus_experimental_current.txt b/ui/ui-platform/api/public_plus_experimental_current.txt index 7cdc7590cea..f288fffcdb4 100644 --- a/ui/ui-platform/api/public_plus_experimental_current.txt +++ b/ui/ui-platform/api/public_plus_experimental_current.txt @@ -140,6 +140,25 @@ package androidx.ui.core { property public final kotlin.jvm.functions.Function3<androidx.ui.core.DrawReceiver,androidx.ui.graphics.Canvas,androidx.ui.core.PxSize,kotlin.Unit>? onPaintWithChildren; } + public final class FocusNode extends androidx.ui.core.ComponentNode { + ctor public FocusNode(); + method public boolean captureFocus(); + method public boolean freeFocus(); + method public androidx.ui.focus.FocusDetailedState getFocusState(); + method public androidx.ui.core.LayoutCoordinates? getLayoutCoordinates(); + method public kotlin.jvm.functions.Function0<kotlin.Unit> getRecompose(); + method public androidx.ui.core.Ref<androidx.ui.core.FocusNode>? getRef(); + method public void requestFocus(boolean propagateFocus = true); + method public void setFocusState$lintWithKotlin(androidx.ui.focus.FocusDetailedState p); + method public void setLayoutCoordinates(androidx.ui.core.LayoutCoordinates? p); + method public void setRecompose(kotlin.jvm.functions.Function0<kotlin.Unit> value); + method public void setRef(androidx.ui.core.Ref<androidx.ui.core.FocusNode>? value); + property public final androidx.ui.focus.FocusDetailedState focusState; + property public final androidx.ui.core.LayoutCoordinates? layoutCoordinates; + property public final kotlin.jvm.functions.Function0<kotlin.Unit> recompose; + property public final androidx.ui.core.Ref<androidx.ui.core.FocusNode>? ref; + } + public final class LayoutNode extends androidx.ui.core.ComponentNode implements androidx.ui.core.Measurable { ctor public LayoutNode(); method public boolean getAffectsParentSize(); @@ -324,6 +343,15 @@ package androidx.ui.core { } +package androidx.ui.core.focus { + + public final class FocusNodeUtilsKt { + ctor public FocusNodeUtilsKt(); + method public static void initializeFocusState(androidx.ui.core.FocusNode); + } + +} + package androidx.ui.core.pointerinput { public final class MotionEventAdapterKt { diff --git a/ui/ui-platform/api/restricted_0.1.0-dev03.txt b/ui/ui-platform/api/restricted_0.1.0-dev03.txt index 7cdc7590cea..f288fffcdb4 100644 --- a/ui/ui-platform/api/restricted_0.1.0-dev03.txt +++ b/ui/ui-platform/api/restricted_0.1.0-dev03.txt @@ -140,6 +140,25 @@ package androidx.ui.core { property public final kotlin.jvm.functions.Function3<androidx.ui.core.DrawReceiver,androidx.ui.graphics.Canvas,androidx.ui.core.PxSize,kotlin.Unit>? onPaintWithChildren; } + public final class FocusNode extends androidx.ui.core.ComponentNode { + ctor public FocusNode(); + method public boolean captureFocus(); + method public boolean freeFocus(); + method public androidx.ui.focus.FocusDetailedState getFocusState(); + method public androidx.ui.core.LayoutCoordinates? getLayoutCoordinates(); + method public kotlin.jvm.functions.Function0<kotlin.Unit> getRecompose(); + method public androidx.ui.core.Ref<androidx.ui.core.FocusNode>? getRef(); + method public void requestFocus(boolean propagateFocus = true); + method public void setFocusState$lintWithKotlin(androidx.ui.focus.FocusDetailedState p); + method public void setLayoutCoordinates(androidx.ui.core.LayoutCoordinates? p); + method public void setRecompose(kotlin.jvm.functions.Function0<kotlin.Unit> value); + method public void setRef(androidx.ui.core.Ref<androidx.ui.core.FocusNode>? value); + property public final androidx.ui.focus.FocusDetailedState focusState; + property public final androidx.ui.core.LayoutCoordinates? layoutCoordinates; + property public final kotlin.jvm.functions.Function0<kotlin.Unit> recompose; + property public final androidx.ui.core.Ref<androidx.ui.core.FocusNode>? ref; + } + public final class LayoutNode extends androidx.ui.core.ComponentNode implements androidx.ui.core.Measurable { ctor public LayoutNode(); method public boolean getAffectsParentSize(); @@ -324,6 +343,15 @@ package androidx.ui.core { } +package androidx.ui.core.focus { + + public final class FocusNodeUtilsKt { + ctor public FocusNodeUtilsKt(); + method public static void initializeFocusState(androidx.ui.core.FocusNode); + } + +} + package androidx.ui.core.pointerinput { public final class MotionEventAdapterKt { diff --git a/ui/ui-platform/api/restricted_current.txt b/ui/ui-platform/api/restricted_current.txt index 7cdc7590cea..f288fffcdb4 100644 --- a/ui/ui-platform/api/restricted_current.txt +++ b/ui/ui-platform/api/restricted_current.txt @@ -140,6 +140,25 @@ package androidx.ui.core { property public final kotlin.jvm.functions.Function3<androidx.ui.core.DrawReceiver,androidx.ui.graphics.Canvas,androidx.ui.core.PxSize,kotlin.Unit>? onPaintWithChildren; } + public final class FocusNode extends androidx.ui.core.ComponentNode { + ctor public FocusNode(); + method public boolean captureFocus(); + method public boolean freeFocus(); + method public androidx.ui.focus.FocusDetailedState getFocusState(); + method public androidx.ui.core.LayoutCoordinates? getLayoutCoordinates(); + method public kotlin.jvm.functions.Function0<kotlin.Unit> getRecompose(); + method public androidx.ui.core.Ref<androidx.ui.core.FocusNode>? getRef(); + method public void requestFocus(boolean propagateFocus = true); + method public void setFocusState$lintWithKotlin(androidx.ui.focus.FocusDetailedState p); + method public void setLayoutCoordinates(androidx.ui.core.LayoutCoordinates? p); + method public void setRecompose(kotlin.jvm.functions.Function0<kotlin.Unit> value); + method public void setRef(androidx.ui.core.Ref<androidx.ui.core.FocusNode>? value); + property public final androidx.ui.focus.FocusDetailedState focusState; + property public final androidx.ui.core.LayoutCoordinates? layoutCoordinates; + property public final kotlin.jvm.functions.Function0<kotlin.Unit> recompose; + property public final androidx.ui.core.Ref<androidx.ui.core.FocusNode>? ref; + } + public final class LayoutNode extends androidx.ui.core.ComponentNode implements androidx.ui.core.Measurable { ctor public LayoutNode(); method public boolean getAffectsParentSize(); @@ -324,6 +343,15 @@ package androidx.ui.core { } +package androidx.ui.core.focus { + + public final class FocusNodeUtilsKt { + ctor public FocusNodeUtilsKt(); + method public static void initializeFocusState(androidx.ui.core.FocusNode); + } + +} + package androidx.ui.core.pointerinput { public final class MotionEventAdapterKt { diff --git a/ui/ui-platform/src/main/java/androidx/ui/core/ComponentNodes.kt b/ui/ui-platform/src/main/java/androidx/ui/core/ComponentNodes.kt index ded520d0e2a..2b28c5cb9ba 100644 --- a/ui/ui-platform/src/main/java/androidx/ui/core/ComponentNodes.kt +++ b/ui/ui-platform/src/main/java/androidx/ui/core/ComponentNodes.kt @@ -17,8 +17,17 @@ package androidx.ui.core import androidx.compose.Emittable import androidx.ui.core.semantics.SemanticsConfiguration +import androidx.ui.focus.FocusDetailedState.Active +import androidx.ui.focus.FocusDetailedState.Inactive +import androidx.ui.focus.FocusDetailedState.Disabled +import androidx.ui.focus.FocusDetailedState.Captured +import androidx.ui.focus.FocusDetailedState.ActiveParent import androidx.ui.graphics.Canvas import androidx.ui.engine.geometry.Shape +import androidx.ui.core.focus.findParentFocusNode +import androidx.ui.core.focus.ownerHasFocus +import androidx.ui.core.focus.requestFocusForOwner +import androidx.ui.focus.FocusDetailedState import kotlin.properties.ReadWriteProperty import kotlin.reflect.KProperty @@ -346,6 +355,289 @@ class PointerInputNode : ComponentNode() { } /** + * Backing node that implements focus. + */ +class FocusNode : ComponentNode() { + /** + * Implementation oddity around composition; used to capture a reference to this + * [FocusNode] when composed. This is a reverse property that mutates its right-hand side. + * + * TODO: Once we finalize the API consider removing this and replace this with an + * interface that sets the value as a property on the object that needs it. + */ + var ref: Ref<FocusNode>? + get() = null + set(value) { + value?.value = this + } + + /** + * The recompose function of the Recompose component this [FocusNode] is hosted in. + * + * We need to trigger re-composition manually because we determine focus during composition, and + * editing an @Model object during composition does not trigger a re-composition. + * + * TODO (b/144897112): Remove manual recomposition. + */ + private lateinit var _recompose: () -> Unit + var recompose: () -> Unit + get() = _recompose + set(value) { _recompose = value } + + /** + * The focus state for the current component. When the component is in the [Active] state, it + * receives key events and other actions. We use [FocusDetailedState]s internally and + * developers have the option to build their components using [FocusDetailedState], or a + * subset of states defined in [FocusState][androidx.ui.focus.FocusState]. + */ + var focusState: FocusDetailedState = Inactive + internal set + + /** + * The [LayoutCoordinates] of the [OnChildPositioned][androidx.ui.core.OnChildPositioned] + * component that hosts the child components of this [FocusNode]. + */ + @Suppress("KDocUnresolvedReference") + var layoutCoordinates: LayoutCoordinates? = null + + /** + * The list of focusable children of this [FocusNode]. The [ComponentNode] base class defines + * [children] of this node, but the [focusableChildren] set includes all the [FocusNode]s + * that are directly reachable from this [FocusNode]. + */ + private val focusableChildren = mutableSetOf<FocusNode>() + + /** + * The [FocusNode] from the set of [focusableChildren] that is currently [Active]. + */ + private var focusedChild: FocusNode? = null + + /** + * Add this focusable child to the parent's focusable children list. + */ + override fun attach(owner: Owner) { + findParentFocusNode()?.focusableChildren?.add(this) + super.attach(owner) + } + + /** + * Remove this focusable child from the parent's focusable children list. + */ + override fun detach() { + // TODO (b/144119129): If this node is focused, let the parent know that it needs to + // grant focus to another focus node. + super.detach() + findParentFocusNode()?.focusableChildren?.remove(this) + } + + /** + * Request focus for this node. + * + * @param propagateFocus Whether the focus should be propagated to the node's children. + * + * In Compose, the parent [FocusNode] controls focus for its focusable children.Calling this + * function will send a focus request to this [FocusNode]'s parent [FocusNode]. + */ + fun requestFocus(propagateFocus: Boolean = true) { + + when (focusState) { + Active, Captured, Disabled -> return + ActiveParent -> { + /** We don't need to do anything if [propagateFocus] is true, + since this subtree already has focus.*/ + if (!propagateFocus && focusedChild?.clearFocus() ?: true) { + grantFocus(propagateFocus) + } + } + Inactive -> { + val focusParent = findParentFocusNode() + if (focusParent == null) { + // TODO (b/144116848) : Find out if the view hosting this composable is in focus. + // The top most focusable is [Active] only if the view hosting this composable is + // in focus. For now, we are making the assumption that our activity has only one + // view, and it is always in focus. + // Also, if the host AndroidComposeView does not have focus, request focus. + // Proceed to grant focus to this node only if the host view gains focus. + grantFocus(propagateFocus) + recompose() + } else { + focusParent.requestFocusForChild(this, propagateFocus) + } + } + } + } + + /** + * Deny requests to clear focus. + * + * This is used when a component wants to hold onto focus (eg. A phone number field with an + * invalid number. + * + * @return true if the focus was successfully captured. False otherwise. + */ + fun captureFocus(): Boolean { + if (focusState == Active) { + focusState = Captured + return true + } else { + return false + } + } + + /** + * When the node is in the [Captured] state, it rejects all requests to clear focus. Calling + * [freeFocus] puts the node in the [Active] state, where it is no longer preventing other + * nodes from requesting focus. + * + * @return true if the captured focus was released. If the node is not in the [Captured] + * state. this function returns false to indicate that this operation was a no-op. + */ + fun freeFocus(): Boolean { + if (focusState == Captured) { + focusState = Active + return true + } else { + return false + } + } + + /** + * This function grants focus to this node. + * + * @param propagateFocus Whether the focus should be propagated to the node's children. + * + * Note: This function is private, and should only be called by a parent [FocusNode] to grant + * focus to one of its child [FocusNode]s. + */ + private fun grantFocus(propagateFocus: Boolean) { + + // TODO (b/144126570) use ChildFocusablility. + // For now we assume children get focus before parent). + + // TODO (b/144126759): Design a system to decide which child get's focus. + // for now we grant focus to the first child. + val focusedCandidate = focusableChildren.firstOrNull() + + if (focusedCandidate == null || !propagateFocus) { + // No Focused Children, or we don't want to propagate focus to children. + focusState = Active + } else { + focusState = ActiveParent + focusedChild = focusedCandidate + focusedCandidate.grantFocus(propagateFocus) + focusedCandidate.recompose() + } + } + + /** + * This function clears focus from this node. + * + * Note: This function is private, and should only be called by a parent [FocusNode] to clear + * focus from one of its child [FocusNode]s. + */ + private fun clearFocus(): Boolean { + return when (focusState) { + + Active -> { + focusState = Inactive + true + } + /** + * If the node is [ActiveParent], we need to clear focus from the [Active] descendant + * first, before clearing focus of this node. + */ + ActiveParent -> focusedChild?.clearFocus() ?: error("No Focused Child") + /** + * If the node is [Captured], deny requests to clear focus. + */ + Captured -> false + /** + * Nothing to do if the node is not focused. Even though the node ends up in a + * cleared state, we return false to indicate that we didn't change any state (This + * return value is used to trigger a recomposition, so returning false will not + * trigger any recomposition). + */ + Inactive, Disabled -> false + } + } + + /** + * Focusable children of this [FocusNode] can use this function to request focus. + * + * @param childNode: The node that is requesting focus. + * @param propagateFocus Whether the focus should be propagated to the node's children. + * @return true if focus was granted, false otherwise. + */ + private fun requestFocusForChild(childNode: FocusNode, propagateFocus: Boolean): Boolean { + + // Only this node's children can ask for focus. + if (!focusableChildren.contains(childNode)) { + error("Non child node cannot request focus.") + } + + return when (focusState) { + /** + * If this node is [Active], it can give focus to the requesting child. + */ + Active -> { + focusState = ActiveParent + focusedChild = childNode + childNode.grantFocus(propagateFocus) + recompose() + true + } + /** + * If this node is [ActiveParent] ie, one of the parent's descendants is [Active], + * remove focus from the currently focused child and grant it to the requesting child. + */ + ActiveParent -> { + val previouslyFocusedNode = focusedChild ?: error("no focusedChild found") + if (previouslyFocusedNode.clearFocus()) { + focusedChild = childNode + childNode.grantFocus(propagateFocus) + previouslyFocusedNode.recompose() + childNode.recompose() + true + } else { + // Currently focused component does not want to give up focus. + false + } + } + /** + * If this node is not [Active], we must gain focus first before granting it + * to the requesting child. + */ + Inactive -> { + val focusParent = findParentFocusNode() + if (focusParent == null) { + requestFocusForOwner() + // If the owner successfully gains focus, proceed otherwise return false. + if (ownerHasFocus()) { + focusState = Active + requestFocusForChild(childNode, propagateFocus) + } else { + false + } + } else if (focusParent.requestFocusForChild(this, propagateFocus = false)) { + requestFocusForChild(childNode, propagateFocus) + } else { + // Could not gain focus, so have no focus to give. + false + } + } + /** + * If this node is [Captured], decline requests from the children. + */ + Captured -> false + /** + * Children of a [Disabled] parent should also be [Disabled]. + */ + Disabled -> error("non root FocusNode needs a focusable parent") + } + } +} + +/** * Backing node for the Draw component. */ class DrawNode : ComponentNode() { @@ -404,6 +696,7 @@ class LayoutNode : ComponentNode(), Measurable { measurables: List<Measurable>, constraints: Constraints ): MeasureScope.LayoutResult + /** * The function used to calculate [IntrinsicMeasurable.minIntrinsicWidth]. */ @@ -412,6 +705,7 @@ class LayoutNode : ComponentNode(), Measurable { measurables: List<IntrinsicMeasurable>, h: IntPx ): IntPx + /** * The lambda used to calculate [IntrinsicMeasurable.minIntrinsicHeight]. */ @@ -420,6 +714,7 @@ class LayoutNode : ComponentNode(), Measurable { measurables: List<IntrinsicMeasurable>, w: IntPx ): IntPx + /** * The function used to calculate [IntrinsicMeasurable.maxIntrinsicWidth]. */ @@ -428,6 +723,7 @@ class LayoutNode : ComponentNode(), Measurable { measurables: List<IntrinsicMeasurable>, h: IntPx ): IntPx + /** * The lambda used to calculate [IntrinsicMeasurable.maxIntrinsicHeight]. */ @@ -1097,28 +1393,31 @@ class SemanticsComponentNode( * merged with the semantics of any ancestors (if the ancestor allows that). * * Whether descendants of this composable can add their semantic information to the - * [SemanticsNode] introduced by this configuration is controlled by - * [explicitChildNodes]. + * [SemanticsNode][androidx.ui.core.semantics.SemanticNode] introduced by this configuration is + * controlled by [explicitChildNodes]. */ + @Suppress("KDocUnresolvedReference") container: Boolean = false, /** * Whether descendants of this composable are allowed to add semantic information - * to the [SemanticsNode] annotated by this composable. + * to the [SemanticsNode][androidx.ui.core.semantics.SemanticNode] annotated by this composable. * - * When set to false descendants are allowed to annotate [SemanticNode]s of + * When set to false descendants are allowed to annotate [SemanticNodes][androidx.ui.core + * .semantics.SemanticNode] of * their parent with the semantic information they want to contribute to the * semantic tree. * When set to true the only way for descendants to contribute semantic * information to the semantic tree is to introduce new explicit - * [SemanticNode]s to the tree. + * [SemanticNodes][androidx.ui.core.semantics.SemanticNode] to the tree. * * If the semantics properties of this node include - * [SemanticsProperties.scopesRoute] set to true, then [explicitChildNodes] - * must be true also. + * [scopesRoute][androidx.ui.semantics.SemanticsProperties.scopesRoute] set to + * true, then [explicitChildNodes] must be true also. * * This setting is often used in combination with [SemanticsConfiguration.isSemanticBoundary] * to create semantic boundaries that are either writable or not for children. */ + @Suppress("KDocUnresolvedReference") explicitChildNodes: Boolean = false ) : ComponentNode() { private var needsSemanticsUpdate = true diff --git a/ui/ui-platform/src/main/java/androidx/ui/core/focus/FocusNodeUtils.kt b/ui/ui-platform/src/main/java/androidx/ui/core/focus/FocusNodeUtils.kt new file mode 100644 index 00000000000..795de8fb7be --- /dev/null +++ b/ui/ui-platform/src/main/java/androidx/ui/core/focus/FocusNodeUtils.kt @@ -0,0 +1,55 @@ +/* + * Copyright 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package androidx.ui.core.focus + +import androidx.ui.core.FocusNode + +/** + * Find the first ancestor that is a [FocusNode]. + */ +internal fun FocusNode.findParentFocusNode(): FocusNode? { + var focusableParent = parent + while (focusableParent != null) { + if (focusableParent is FocusNode) { + return focusableParent + } else { + focusableParent = focusableParent.parent + } + } + return null +} + +internal fun FocusNode.ownerHasFocus(): Boolean { + // TODO(b/144895515): Read the focus state from the owner. + return true +} + +internal fun FocusNode.requestFocusForOwner() { + // TODO(b/144893832): Ask the owner to request focus. +} + +/** + * Checks the focus state of the [Owner][androidx.ui.core.Owner] and Initializes the focus state of + * the node. + * + * Note: This function acts only on the root node. It is a no-op for other nodes. + */ +fun FocusNode.initializeFocusState() { + if (findParentFocusNode() == null && ownerHasFocus()) { + requestFocus() + } +}
\ No newline at end of file diff --git a/ui/ui-platform/src/test/java/androidx/ui/core/FocusNodeTest.kt b/ui/ui-platform/src/test/java/androidx/ui/core/FocusNodeTest.kt new file mode 100644 index 00000000000..3a6b3614f02 --- /dev/null +++ b/ui/ui-platform/src/test/java/androidx/ui/core/FocusNodeTest.kt @@ -0,0 +1,112 @@ +/* + * Copyright 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package androidx.ui.core + +import androidx.test.filters.SmallTest +import androidx.ui.focus.FocusDetailedState.Active +import androidx.ui.focus.FocusDetailedState.Captured +import androidx.ui.focus.FocusDetailedState.Inactive +import com.google.common.truth.Truth.assertThat +import org.junit.Test +import org.junit.runner.RunWith +import org.junit.runners.JUnit4 + +@SmallTest +@RunWith(JUnit4::class) +class FocusNodeTest { + + @Test + fun captureReferenceToFocusNode() { + // Arrange. + val focusNode = FocusNode() + val focusNodeRef = Ref<FocusNode>() + + // Act. + focusNode.ref = focusNodeRef + + // Assert. + assertThat(focusNodeRef.value).isEqualTo(focusNode) + } + + @Test + fun defaultFocusState() { + // Arrange. + val focusNode = FocusNode() + + // Assert. + assertThat(focusNode.focusState).isEqualTo(Inactive) + } + + @Test + fun defaultLayoutCoordinates() { + // Arrange. + val focusNode = FocusNode() + + // Assert. + assertThat(focusNode.layoutCoordinates).isNull() + } + + @Test + fun captureFocusfromActiveState() { + // Arrange. + val focusNode = FocusNode().apply { focusState = Active } + + // Act. + focusNode.captureFocus() + + // Assert. + assertThat(focusNode.focusState).isEqualTo(Captured) + } + + @Test + fun captureFocusfromNonActiveState() { + // Arrange. + val focusNode = FocusNode().apply { focusState = Inactive } + + // Act. + focusNode.captureFocus() + + // Assert. + assertThat(focusNode.focusState).isEqualTo(Inactive) + } + + @Test + fun freeFocusfromCapturedState() { + // Arrange. + val focusNode = FocusNode().apply { focusState = Captured } + + // Act. + focusNode.freeFocus() + + // Assert. + assertThat(focusNode.focusState).isEqualTo( + Active + ) + } + + @Test + fun freeFocusfromNonActiveState() { + // Arrange. + val focusNode = FocusNode().apply { focusState = Inactive } + + // Act. + focusNode.freeFocus() + + // Assert. + assertThat(focusNode.focusState).isEqualTo(Inactive) + } +}
\ No newline at end of file diff --git a/ui/ui-platform/src/test/java/androidx/ui/core/focus/FindParentFocusNodeTest.kt b/ui/ui-platform/src/test/java/androidx/ui/core/focus/FindParentFocusNodeTest.kt new file mode 100644 index 00000000000..d29c95fac0b --- /dev/null +++ b/ui/ui-platform/src/test/java/androidx/ui/core/focus/FindParentFocusNodeTest.kt @@ -0,0 +1,91 @@ +/* + * Copyright 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package androidx.ui.core.focus + +import androidx.test.filters.SmallTest +import androidx.ui.core.FocusNode +import androidx.ui.core.LayoutNode +import androidx.ui.core.PointerInputNode +import com.google.common.truth.Truth.assertThat +import org.junit.Test +import org.junit.runner.RunWith +import org.junit.runners.JUnit4 + +@SmallTest +@RunWith(JUnit4::class) +class FindParentFocusNodeTest { + + @Test + fun noParentReturnsNull() { + // Arrange. + val focusNode = FocusNode() + + // Act. + val parentFousNode = focusNode.findParentFocusNode() + + // Assert. + assertThat(parentFousNode).isNull() + } + + @Test + fun returnsParent() { + // Arrange. + val focusNode = FocusNode() + val parentFocusNode = FocusNode() + parentFocusNode.emitInsertAt(0, focusNode) + + // Act. + val parent = focusNode.findParentFocusNode() + + // Assert. + assertThat(parent).isEqualTo(parentFocusNode) + } + + @Test + fun returnsImmediateParent() { + // Arrange. + val focusNode = FocusNode() + val parentFocusNode = FocusNode() + val grandparentFocusNode = FocusNode() + grandparentFocusNode.emitInsertAt(0, parentFocusNode) + parentFocusNode.emitInsertAt(0, focusNode) + + // Act. + val parent = focusNode.findParentFocusNode() + + // Assert. + assertThat(parent).isEqualTo(parentFocusNode) + } + + @Test + fun ignoresIntermediateComponentNodes() { + // Arrange. + val focusNode = FocusNode() + val intermediatePointerInputNode = PointerInputNode() + val intermediateLayoutNode = LayoutNode() + val parentFocusNode = FocusNode() + parentFocusNode.emitInsertAt(0, intermediatePointerInputNode) + intermediatePointerInputNode.emitInsertAt(0, intermediateLayoutNode) + intermediateLayoutNode.emitInsertAt(0, focusNode) + + // Act. + val parent = focusNode.findParentFocusNode() + + // Assert. + assertThat(parent).isEqualTo(parentFocusNode) + } +}
\ No newline at end of file diff --git a/ui/ui-platform/src/test/java/androidx/ui/core/focus/RequestFocusTest.kt b/ui/ui-platform/src/test/java/androidx/ui/core/focus/RequestFocusTest.kt new file mode 100644 index 00000000000..2c271dbc8f5 --- /dev/null +++ b/ui/ui-platform/src/test/java/androidx/ui/core/focus/RequestFocusTest.kt @@ -0,0 +1,405 @@ +/* + * Copyright 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package androidx.ui.core.focus + +import androidx.test.filters.SmallTest +import androidx.ui.core.FocusNode +import androidx.ui.core.Owner +import androidx.ui.focus.FocusDetailedState.Active +import androidx.ui.focus.FocusDetailedState.ActiveParent +import androidx.ui.focus.FocusDetailedState.Captured +import androidx.ui.focus.FocusDetailedState.Disabled +import androidx.ui.focus.FocusDetailedState.Inactive + +import com.google.common.truth.Truth.assertThat +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith +import org.junit.runners.Parameterized +import org.mockito.Mockito.mock + +@SmallTest +@RunWith(Parameterized::class) +class RequestFocusTest(val propagateFocus: Boolean) { + lateinit var host: Owner + + @Before + fun setup() { + host = mock(Owner::class.java) + } + + companion object { + @JvmStatic + @Parameterized.Parameters(name = "propagateFocus = {0}") + fun initParameters() = listOf(true, false) + } + + @Test + fun activeComponent() { + // Arrange. + val focusNode = FocusNode().apply { + focusState = Active + recompose = {} + } + + // Act. + focusNode.requestFocus(propagateFocus) + + // Assert. + assertThat(focusNode.focusState).isEqualTo(Active) + } + + @Test + fun capturedComponent() { + // Arrange. + val focusNode = FocusNode().apply { + focusState = Captured + recompose = {} + } + + // Act. + focusNode.requestFocus(propagateFocus) + + // Assert. + assertThat(focusNode.focusState).isEqualTo(Captured) + } + + @Test + fun disabledComponent() { + // Arrange. + val focusNode = FocusNode().apply { + focusState = Disabled + recompose = {} + } + + // Act. + focusNode.requestFocus(propagateFocus) + + // Assert. + assertThat(focusNode.focusState).isEqualTo(Disabled) + } + + @Test + fun rootNode() { + // Arrange. + val rootNode = FocusNode().apply { recompose = {} } + + // Act. + rootNode.requestFocus(propagateFocus) + + // Assert. + assertThat(rootNode.focusState).isEqualTo(Active) + } + + @Test + fun rootNodeWithChildren() { + // Arrange. + val childNode = FocusNode().apply { recompose = {} } + val rootNode = FocusNode().apply { + recompose = {} + attach(host) + emitInsertAt(0, childNode) + } + + // Act. + rootNode.requestFocus(propagateFocus) + + // Assert. + when (propagateFocus) { + true -> assertThat(rootNode.focusState).isEqualTo(ActiveParent) + false -> assertThat(rootNode.focusState).isEqualTo(Active) + } + } + + @Test + fun parentNodeWithNoFocusedAncestor() { + // Arrange. + val childNode = FocusNode().apply { recompose = {} } + val parentNode = FocusNode().apply { recompose = {} } + val grandparentNode = FocusNode().apply { + recompose = {} + attach(host) + emitInsertAt(0, parentNode) + } + parentNode.emitInsertAt(0, childNode) + + // Act. + parentNode.requestFocus(propagateFocus) + + // Assert. + when (propagateFocus) { + true -> assertThat(parentNode.focusState).isEqualTo(ActiveParent) + false -> assertThat(parentNode.focusState).isEqualTo(Active) + } + } + + @Test + fun parentNodeWithNoFocusedAncestor_childRequestsFocus() { + // Arrange. + val childNode = FocusNode().apply { recompose = {} } + val parentNode = FocusNode().apply { recompose = {} } + val grandparentNode = FocusNode().apply { + recompose = {} + attach(host) + emitInsertAt(0, parentNode) + } + parentNode.emitInsertAt(0, childNode) + + // Act. + childNode.requestFocus(propagateFocus) + + // Assert. + assertThat(parentNode.focusState).isEqualTo(ActiveParent) + } + + @Test + fun childNodeWithNoFocusedAncestor() { + // Arrange. + val childNode = FocusNode().apply { recompose = {} } + val parentNode = FocusNode().apply { recompose = {} } + val grandparentNode = FocusNode().apply { + recompose = {} + attach(host) + emitInsertAt(0, parentNode) + } + parentNode.emitInsertAt(0, childNode) + + // Act. + childNode.requestFocus(propagateFocus) + + // Assert. + assertThat(childNode.focusState).isEqualTo(Active) + } + + @Test + fun requestFocus_parentIsFocused() { + // Arrange. + val focusNode = FocusNode().apply { recompose = {} } + val parentNode = FocusNode().apply { + attach(host) + focusState = Active + recompose = {} + emitInsertAt(0, focusNode) + } + + // Verify Setup. + assertThat(parentNode.focusState).isEqualTo(Active) + assertThat(focusNode.focusState).isEqualTo(Inactive) + + // After executing requestFocus, siblingNode will be 'Active'. + focusNode.requestFocus(propagateFocus) + + // Assert. + assertThat(parentNode.focusState).isEqualTo(ActiveParent) + assertThat(focusNode.focusState).isEqualTo(Active) + } + + @Test + fun requestFocus_childIsFocused() { + // Arrange. + val focusNode = FocusNode().apply { recompose = {} } + val parentNode = FocusNode().apply { + attach(host) + recompose = {} + emitInsertAt(0, focusNode) + } + focusNode.requestFocus(propagateFocus) + + // Verify Setup. + assertThat(parentNode.focusState).isEqualTo(ActiveParent) + assertThat(focusNode.focusState).isEqualTo(Active) + + // Act. + parentNode.requestFocus(propagateFocus) + + // Assert. + when (propagateFocus) { + true -> { + assertThat(parentNode.focusState).isEqualTo(ActiveParent) + assertThat(focusNode.focusState).isEqualTo(Active) + } + false -> { + assertThat(parentNode.focusState).isEqualTo(Active) + assertThat(focusNode.focusState).isEqualTo(Inactive) + } + } + } + + @Test + fun requestFocus_childHasCapturedFocus() { + // Arrange. + val focusNode = FocusNode().apply { recompose = {} } + val parentNode = FocusNode().apply { + attach(host) + recompose = {} + emitInsertAt(0, focusNode) + } + focusNode.apply { + requestFocus(propagateFocus) + captureFocus() + } + + // Verify Setup. + assertThat(parentNode.focusState).isEqualTo(ActiveParent) + assertThat(focusNode.focusState).isEqualTo(Captured) + + // Act. + parentNode.requestFocus(propagateFocus) + + // Assert. + assertThat(parentNode.focusState).isEqualTo(ActiveParent) + assertThat(focusNode.focusState).isEqualTo(Captured) + } + + @Test + fun requestFocus_siblingIsFocused() { + // Arrange. + val focusNode = FocusNode().apply { recompose = {} } + val siblingNode = FocusNode().apply { recompose = {} } + val parentNode = FocusNode().apply { + recompose = {} + focusState = Active + attach(host) + emitInsertAt(0, focusNode) + emitInsertAt(1, siblingNode) + } + // After executing requestFocus, siblingNode will be 'Active'. + siblingNode.requestFocus(propagateFocus) + + // Verify Setup. + assertThat(parentNode.focusState).isEqualTo(ActiveParent) + assertThat(focusNode.focusState).isEqualTo(Inactive) + assertThat(siblingNode.focusState).isEqualTo(Active) + + // Act. + focusNode.requestFocus(propagateFocus) + + // Assert. + assertThat(parentNode.focusState).isEqualTo(ActiveParent) + assertThat(focusNode.focusState).isEqualTo(Active) + assertThat(siblingNode.focusState).isEqualTo(Inactive) + } + + @Test + fun requestFocus_siblingHasCapturedFocused() { + // Arrange. + val focusNode = FocusNode().apply { recompose = {} } + val siblingNode = FocusNode().apply { recompose = {} } + val parentNode = FocusNode().apply { + recompose = {} + focusState = Active + attach(host) + emitInsertAt(0, focusNode) + emitInsertAt(1, siblingNode) + } + // After executing requestFocus, siblingNode will be 'Active'. + siblingNode.apply { + requestFocus(propagateFocus) + captureFocus() + } + + // Verify Setup. + assertThat(parentNode.focusState).isEqualTo(ActiveParent) + assertThat(focusNode.focusState).isEqualTo(Inactive) + assertThat(siblingNode.focusState).isEqualTo(Captured) + + // Act. + focusNode.requestFocus(propagateFocus) + + // Assert. + assertThat(parentNode.focusState).isEqualTo(ActiveParent) + assertThat(focusNode.focusState).isEqualTo(Inactive) + assertThat(siblingNode.focusState).isEqualTo(Captured) + } + + @Test + fun requestFocus_cousinIsFocused() { + // Arrange. + val focusNode = FocusNode().apply { + focusState = Inactive + recompose = {} + } + + val cousinNode = FocusNode().apply { + focusState = Inactive + recompose = {} + } + + val parentNode = FocusNode().apply { + focusState = Inactive + recompose = {} + } + val auntNode = FocusNode().apply { + focusState = Inactive + recompose = {} + } + + val grandparentNode = FocusNode().apply { + focusState = Active + recompose = {} + } + + grandparentNode.apply { + attach(host) + emitInsertAt(0, parentNode) + emitInsertAt(1, auntNode) + } + + parentNode.emitInsertAt(0, focusNode) + auntNode.emitInsertAt(0, cousinNode) + cousinNode.requestFocus(propagateFocus) + + // Verify Setup. + assertThat(cousinNode.focusState).isEqualTo(Active) + assertThat(focusNode.focusState).isEqualTo(Inactive) + + // Act. + focusNode.requestFocus(propagateFocus) + + // Assert. + assertThat(cousinNode.focusState).isEqualTo(Inactive) + assertThat(focusNode.focusState).isEqualTo(Active) + } + + @Test + fun requestFocus_grandParentIsFocused() { + // Arrange. + val focusNode = FocusNode().apply { recompose = {} } + val parentNode = FocusNode().apply { recompose = {} } + val grandparentNode = FocusNode().apply { recompose = {} } + + grandparentNode.apply { + attach(host) + emitInsertAt(0, parentNode) + focusState = Active + } + parentNode.emitInsertAt(0, focusNode) + + // Verify Setup. + assertThat(grandparentNode.focusState).isEqualTo(Active) + assertThat(parentNode.focusState).isEqualTo(Inactive) + assertThat(focusNode.focusState).isEqualTo(Inactive) + + // Act. + focusNode.requestFocus(propagateFocus) + + // Assert. + assertThat(grandparentNode.focusState).isEqualTo(ActiveParent) + assertThat(parentNode.focusState).isEqualTo(ActiveParent) + assertThat(focusNode.focusState).isEqualTo(Active) + } +}
\ No newline at end of file diff --git a/ui/ui-test/build.gradle b/ui/ui-test/build.gradle index b955db81aab..78872d5c478 100644 --- a/ui/ui-test/build.gradle +++ b/ui/ui-test/build.gradle @@ -29,7 +29,7 @@ plugins { } dependencies { - kotlinPlugin project(path: ":compose:compose-compiler", configuration: "embeddablePlugin") + kotlinPlugin project(path: ":compose:compose-compiler") implementation(KOTLIN_STDLIB) implementation(ANDROIDX_TEST_RULES) diff --git a/ui/ui-text/build.gradle b/ui/ui-text/build.gradle index 9305e319091..185fbee2e09 100644 --- a/ui/ui-text/build.gradle +++ b/ui/ui-text/build.gradle @@ -29,7 +29,7 @@ plugins { } dependencies { - kotlinPlugin project(path: ":compose:compose-compiler", configuration: "embeddablePlugin") + kotlinPlugin project(path: ":compose:compose-compiler") implementation(KOTLIN_STDLIB) // TODO: Non-Kotlin dependency, move to Android-specific code diff --git a/ui/ui-text/integration-tests/samples/build.gradle b/ui/ui-text/integration-tests/samples/build.gradle index d6194ef8553..b58854bb61b 100644 --- a/ui/ui-text/integration-tests/samples/build.gradle +++ b/ui/ui-text/integration-tests/samples/build.gradle @@ -26,7 +26,7 @@ plugins { } dependencies { - kotlinPlugin project(path: ":compose:compose-compiler", configuration: "embeddablePlugin") + kotlinPlugin project(path: ":compose:compose-compiler") implementation(KOTLIN_STDLIB) diff --git a/ui/ui-text/integration-tests/text-demos/build.gradle b/ui/ui-text/integration-tests/text-demos/build.gradle index b71916523e6..5b72f84fb1d 100644 --- a/ui/ui-text/integration-tests/text-demos/build.gradle +++ b/ui/ui-text/integration-tests/text-demos/build.gradle @@ -10,7 +10,7 @@ plugins { } dependencies { - kotlinPlugin project(path: ":compose:compose-compiler", configuration: "embeddablePlugin") + kotlinPlugin project(path: ":compose:compose-compiler") implementation(KOTLIN_STDLIB) diff --git a/ui/ui-tooling/build.gradle b/ui/ui-tooling/build.gradle index 209822315cd..bd5b8a652b1 100644 --- a/ui/ui-tooling/build.gradle +++ b/ui/ui-tooling/build.gradle @@ -29,7 +29,7 @@ plugins { } dependencies { - kotlinPlugin project(path: ":compose:compose-compiler", configuration: "embeddablePlugin") + kotlinPlugin project(path: ":compose:compose-compiler") implementation(KOTLIN_STDLIB) diff --git a/ui/ui-tooling/src/androidTest/java/androidx/ui/tooling/ComposeViewAdapterTest.kt b/ui/ui-tooling/src/androidTest/java/androidx/ui/tooling/ComposeViewAdapterTest.kt index 7d6f77e75ca..3fa2bcfa6e0 100644 --- a/ui/ui-tooling/src/androidTest/java/androidx/ui/tooling/ComposeViewAdapterTest.kt +++ b/ui/ui-tooling/src/androidTest/java/androidx/ui/tooling/ComposeViewAdapterTest.kt @@ -76,6 +76,21 @@ class ComposeViewAdapterTest { } } + @Test + fun previewInClass() { + activityTestRule.runOnUiThread { + composeViewAdapter.init( + "androidx.ui.tooling.TestGroup", + "InClassPreview", + debugViewInfos = true + ) + } + + activityTestRule.runOnUiThread { + assertTrue(composeViewAdapter.viewInfos.isNotEmpty()) + } + } + companion object { class TestActivity : Activity() { override fun onCreate(savedInstanceState: Bundle?) { diff --git a/ui/ui-tooling/src/androidTest/java/androidx/ui/tooling/SimpleComposablePreview.kt b/ui/ui-tooling/src/androidTest/java/androidx/ui/tooling/SimpleComposablePreview.kt index 6f61093b283..f3a214318f1 100644 --- a/ui/ui-tooling/src/androidTest/java/androidx/ui/tooling/SimpleComposablePreview.kt +++ b/ui/ui-tooling/src/androidTest/java/androidx/ui/tooling/SimpleComposablePreview.kt @@ -36,4 +36,14 @@ private fun PrivateSimpleComposablePreview() { Surface(color = Color.Red) { Text("Private Hello world") } +} + +class TestGroup { + @Preview + @Composable + fun InClassPreview() { + Surface(color = Color.Red) { + Text("In class") + } + } }
\ No newline at end of file diff --git a/ui/ui-tooling/src/main/java/androidx/ui/tooling/preview/ComposeViewAdapter.kt b/ui/ui-tooling/src/main/java/androidx/ui/tooling/preview/ComposeViewAdapter.kt index a4080293b1f..652a903fd8a 100644 --- a/ui/ui-tooling/src/main/java/androidx/ui/tooling/preview/ComposeViewAdapter.kt +++ b/ui/ui-tooling/src/main/java/androidx/ui/tooling/preview/ComposeViewAdapter.kt @@ -38,6 +38,7 @@ import androidx.ui.tooling.Group import androidx.ui.tooling.Inspectable import androidx.ui.tooling.asTree import androidx.ui.tooling.tables +import java.lang.reflect.Modifier const val TOOLS_NS_URI = "http://schemas.android.com/tools" @@ -215,7 +216,16 @@ internal class ComposeViewAdapter : FrameLayout { val composableClass = Class.forName(className) val method = composableClass.getDeclaredMethod(methodName) method.isAccessible = true - method.invoke(null) + + if (Modifier.isStatic(method.modifiers)) { + // This is a top level or static method + method.invoke(null) + } else { + // The method is part of a class. We try to instantiate the class with an + // empty constructor. + val instance = composableClass.getConstructor().newInstance() + method.invoke(instance) + } } catch (e: ReflectiveOperationException) { throw ClassNotFoundException("Composable Method not found", e) } diff --git a/ui/ui-vector/build.gradle b/ui/ui-vector/build.gradle index d994acf054d..7a109d3ba13 100644 --- a/ui/ui-vector/build.gradle +++ b/ui/ui-vector/build.gradle @@ -29,7 +29,7 @@ plugins { } dependencies { - kotlinPlugin project(path: ":compose:compose-compiler", configuration: "embeddablePlugin") + kotlinPlugin project(path: ":compose:compose-compiler") api project(':ui:ui-core') implementation "androidx.collection:collection:1.0.0" diff --git a/work/workmanager-lint/src/test/java/androidx/work/lint/BadConfigurationProviderTest.kt b/work/workmanager-lint/src/test/java/androidx/work/lint/BadConfigurationProviderTest.kt index ba4af629f99..771a3f40552 100644 --- a/work/workmanager-lint/src/test/java/androidx/work/lint/BadConfigurationProviderTest.kt +++ b/work/workmanager-lint/src/test/java/androidx/work/lint/BadConfigurationProviderTest.kt @@ -45,7 +45,7 @@ class BadConfigurationProviderTest { WORK_MANAGER_CONFIGURATION_PROVIDER, customApplication ) - .allowMissingSdk() + .sdkHome(sdkDirectory().value) .issues(BadConfigurationProviderIssueDetector.ISSUE) .run() .expectClean() @@ -88,7 +88,7 @@ class BadConfigurationProviderTest { customApplication, invalidProvider ) - .allowMissingSdk() + .sdkHome(sdkDirectory().value) .issues(BadConfigurationProviderIssueDetector.ISSUE) .run() .expect(""" @@ -123,7 +123,7 @@ class BadConfigurationProviderTest { WORK_MANAGER_CONFIGURATION_PROVIDER, customApplication ) - .allowMissingSdk() + .sdkHome(sdkDirectory().value) .issues(BadConfigurationProviderIssueDetector.ISSUE) .run() .expectClean() diff --git a/work/workmanager-lint/src/test/java/androidx/work/lint/Lint.kt b/work/workmanager-lint/src/test/java/androidx/work/lint/Lint.kt new file mode 100644 index 00000000000..915c853040c --- /dev/null +++ b/work/workmanager-lint/src/test/java/androidx/work/lint/Lint.kt @@ -0,0 +1,33 @@ +/* + * Copyright 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package androidx.work.lint + +import java.io.File +import java.io.InputStream +import java.util.Properties + +fun sdkDirectory(): Lazy<File> = lazy { + var stream: InputStream? = null + try { + stream = Stubs::class.java.classLoader.getResourceAsStream("sdk.prop") + val properties = Properties() + properties.load(stream) + File(properties["sdk.dir"] as String) + } finally { + stream?.close() + } +} diff --git a/work/workmanager-lint/src/test/java/androidx/work/lint/PeriodicEnqueueIssueDetectorTest.kt b/work/workmanager-lint/src/test/java/androidx/work/lint/PeriodicEnqueueIssueDetectorTest.kt index ab0dd04ae02..10395fe4fe7 100644 --- a/work/workmanager-lint/src/test/java/androidx/work/lint/PeriodicEnqueueIssueDetectorTest.kt +++ b/work/workmanager-lint/src/test/java/androidx/work/lint/PeriodicEnqueueIssueDetectorTest.kt @@ -54,7 +54,7 @@ class PeriodicEnqueueIssueDetectorTest { PERIODIC_WORK_REQUEST, snippet ) - .allowMissingSdk() + .sdkHome(sdkDirectory().value) .issues(PeriodicEnqueueIssueDetector.ISSUE) .run() .expect(""" @@ -94,7 +94,7 @@ class PeriodicEnqueueIssueDetectorTest { PERIODIC_WORK_REQUEST, snippet ) - .allowMissingSdk() + .sdkHome(sdkDirectory().value) .issues(PeriodicEnqueueIssueDetector.ISSUE) .run() .expect(""" @@ -136,7 +136,7 @@ class PeriodicEnqueueIssueDetectorTest { PERIODIC_WORK_REQUEST, snippet ) - .allowMissingSdk() + .sdkHome(sdkDirectory().value) .issues(PeriodicEnqueueIssueDetector.ISSUE) .run() .expect(""" @@ -175,7 +175,7 @@ class PeriodicEnqueueIssueDetectorTest { PERIODIC_WORK_REQUEST, snippet ) - .allowMissingSdk() + .sdkHome(sdkDirectory().value) .issues(PeriodicEnqueueIssueDetector.ISSUE) .run() .expectClean() @@ -208,7 +208,7 @@ class PeriodicEnqueueIssueDetectorTest { PERIODIC_WORK_REQUEST, snippet ) - .allowMissingSdk() + .sdkHome(sdkDirectory().value) .issues(PeriodicEnqueueIssueDetector.ISSUE) .run() .expectClean() diff --git a/work/workmanager-lint/src/test/java/androidx/work/lint/RemoveWorkManagerInitializerDetectorTest.kt b/work/workmanager-lint/src/test/java/androidx/work/lint/RemoveWorkManagerInitializerDetectorTest.kt index 2a472bf414f..d6dcce2dd93 100644 --- a/work/workmanager-lint/src/test/java/androidx/work/lint/RemoveWorkManagerInitializerDetectorTest.kt +++ b/work/workmanager-lint/src/test/java/androidx/work/lint/RemoveWorkManagerInitializerDetectorTest.kt @@ -68,7 +68,7 @@ class RemoveWorkManagerInitializerDetectorTest { WORK_MANAGER_CONFIGURATION_PROVIDER, customApplication ) - .allowMissingSdk() + .sdkHome(sdkDirectory().value) .issues(RemoveWorkManagerInitializerDetector.ISSUE) .run() .expectClean() @@ -115,7 +115,7 @@ class RemoveWorkManagerInitializerDetectorTest { WORK_MANAGER_CONFIGURATION_PROVIDER, customApplication ) - .allowMissingSdk() + .sdkHome(sdkDirectory().value) .issues(RemoveWorkManagerInitializerDetector.ISSUE) .run() .expectClean() @@ -160,7 +160,7 @@ class RemoveWorkManagerInitializerDetectorTest { WORK_MANAGER_CONFIGURATION_PROVIDER, customApplication ) - .allowMissingSdk() + .sdkHome(sdkDirectory().value) .issues(RemoveWorkManagerInitializerDetector.ISSUE) .run() .expect(""" @@ -214,7 +214,7 @@ class RemoveWorkManagerInitializerDetectorTest { WORK_MANAGER_CONFIGURATION_PROVIDER, customApplication ) - .allowMissingSdk() + .sdkHome(sdkDirectory().value) .issues(RemoveWorkManagerInitializerDetector.ISSUE) .run() .expect(""" |