Merge "Populate the correct ScrollDeltaX/Y values of AccessibilityEvent when making a copy" into androidx-master-dev
diff --git a/activity/activity-ktx/api/1.0.0.txt b/activity/activity-ktx/api/1.0.0.txt
new file mode 100644
index 0000000..c6e9a9a
--- /dev/null
+++ b/activity/activity-ktx/api/1.0.0.txt
@@ -0,0 +1,15 @@
+// Signature format: 3.0
+package androidx.activity {
+
+ public final class ActivityViewModelLazyKt {
+ ctor public ActivityViewModelLazyKt();
+ method @MainThread public static inline <reified VM extends androidx.lifecycle.ViewModel> kotlin.Lazy<VM> viewModels(androidx.activity.ComponentActivity, kotlin.jvm.functions.Function0<? extends androidx.lifecycle.ViewModelProvider.Factory>? factoryProducer = null);
+ }
+
+ public final class OnBackPressedDispatcherKt {
+ ctor public OnBackPressedDispatcherKt();
+ method public static androidx.activity.OnBackPressedCallback addCallback(androidx.activity.OnBackPressedDispatcher, androidx.lifecycle.LifecycleOwner? owner = null, boolean enabled = true, kotlin.jvm.functions.Function1<? super androidx.activity.OnBackPressedCallback,kotlin.Unit> onBackPressed);
+ }
+
+}
+
diff --git a/preference/ktx/api/res-1.0.0.txt b/activity/activity-ktx/api/res-1.0.0.txt
similarity index 100%
copy from preference/ktx/api/res-1.0.0.txt
copy to activity/activity-ktx/api/res-1.0.0.txt
diff --git a/preference/ktx/api/restricted_1.1.0-alpha06.txt b/activity/activity-ktx/api/restricted_1.0.0.txt
similarity index 100%
copy from preference/ktx/api/restricted_1.1.0-alpha06.txt
copy to activity/activity-ktx/api/restricted_1.0.0.txt
diff --git a/activity/activity-ktx/build.gradle b/activity/activity-ktx/build.gradle
index df50b6c..44d017f 100644
--- a/activity/activity-ktx/build.gradle
+++ b/activity/activity-ktx/build.gradle
@@ -35,7 +35,7 @@
dependencies {
api(project(":activity:activity"))
- api("androidx.core:core-ktx:1.1.0-rc01") {
+ api("androidx.core:core-ktx:1.1.0") {
because 'Mirror activity dependency graph for -ktx artifacts'
}
api(project(":lifecycle:lifecycle-runtime-ktx")) {
diff --git a/activity/activity/api/1.0.0.txt b/activity/activity/api/1.0.0.txt
new file mode 100644
index 0000000..9e0f44a
--- /dev/null
+++ b/activity/activity/api/1.0.0.txt
@@ -0,0 +1,37 @@
+// Signature format: 3.0
+package androidx.activity {
+
+ public class ComponentActivity extends androidx.core.app.ComponentActivity implements androidx.lifecycle.LifecycleOwner androidx.activity.OnBackPressedDispatcherOwner androidx.savedstate.SavedStateRegistryOwner androidx.lifecycle.ViewModelStoreOwner {
+ ctor public ComponentActivity();
+ ctor @ContentView public ComponentActivity(@LayoutRes int);
+ method @Deprecated public Object? getLastCustomNonConfigurationInstance();
+ method public final androidx.activity.OnBackPressedDispatcher getOnBackPressedDispatcher();
+ method public final androidx.savedstate.SavedStateRegistry getSavedStateRegistry();
+ method public androidx.lifecycle.ViewModelStore getViewModelStore();
+ method @Deprecated public Object? onRetainCustomNonConfigurationInstance();
+ method public final Object? onRetainNonConfigurationInstance();
+ }
+
+ public abstract class OnBackPressedCallback {
+ ctor public OnBackPressedCallback(boolean);
+ method @MainThread public abstract void handleOnBackPressed();
+ method @MainThread public final boolean isEnabled();
+ method @MainThread public final void remove();
+ method @MainThread public final void setEnabled(boolean);
+ }
+
+ public final class OnBackPressedDispatcher {
+ ctor public OnBackPressedDispatcher();
+ ctor public OnBackPressedDispatcher(Runnable?);
+ method @MainThread public void addCallback(androidx.activity.OnBackPressedCallback);
+ method @MainThread public void addCallback(androidx.lifecycle.LifecycleOwner, androidx.activity.OnBackPressedCallback);
+ method @MainThread public boolean hasEnabledCallbacks();
+ method @MainThread public void onBackPressed();
+ }
+
+ public interface OnBackPressedDispatcherOwner extends androidx.lifecycle.LifecycleOwner {
+ method public androidx.activity.OnBackPressedDispatcher getOnBackPressedDispatcher();
+ }
+
+}
+
diff --git a/activity/activity/api/1.1.0-alpha03.txt b/activity/activity/api/1.1.0-alpha03.txt
index f087fd3..2a6b68b 100644
--- a/activity/activity/api/1.1.0-alpha03.txt
+++ b/activity/activity/api/1.1.0-alpha03.txt
@@ -6,7 +6,6 @@
ctor @ContentView public ComponentActivity(@LayoutRes int);
method public androidx.lifecycle.ViewModelProvider.Factory getDefaultViewModelProviderFactory();
method @Deprecated public Object? getLastCustomNonConfigurationInstance();
- method public androidx.lifecycle.Lifecycle getLifecycle();
method public final androidx.activity.OnBackPressedDispatcher getOnBackPressedDispatcher();
method public final androidx.savedstate.SavedStateRegistry getSavedStateRegistry();
method public androidx.lifecycle.ViewModelStore getViewModelStore();
diff --git a/activity/activity/api/current.txt b/activity/activity/api/current.txt
index f087fd3..2a6b68b 100644
--- a/activity/activity/api/current.txt
+++ b/activity/activity/api/current.txt
@@ -6,7 +6,6 @@
ctor @ContentView public ComponentActivity(@LayoutRes int);
method public androidx.lifecycle.ViewModelProvider.Factory getDefaultViewModelProviderFactory();
method @Deprecated public Object? getLastCustomNonConfigurationInstance();
- method public androidx.lifecycle.Lifecycle getLifecycle();
method public final androidx.activity.OnBackPressedDispatcher getOnBackPressedDispatcher();
method public final androidx.savedstate.SavedStateRegistry getSavedStateRegistry();
method public androidx.lifecycle.ViewModelStore getViewModelStore();
diff --git a/preference/ktx/api/res-1.0.0.txt b/activity/activity/api/res-1.0.0.txt
similarity index 100%
copy from preference/ktx/api/res-1.0.0.txt
copy to activity/activity/api/res-1.0.0.txt
diff --git a/preference/ktx/api/restricted_1.1.0-alpha06.txt b/activity/activity/api/restricted_1.0.0.txt
similarity index 100%
copy from preference/ktx/api/restricted_1.1.0-alpha06.txt
copy to activity/activity/api/restricted_1.0.0.txt
diff --git a/activity/activity/api/restricted_1.1.0-alpha03.txt b/activity/activity/api/restricted_1.1.0-alpha03.txt
index f087fd3..2a6b68b 100644
--- a/activity/activity/api/restricted_1.1.0-alpha03.txt
+++ b/activity/activity/api/restricted_1.1.0-alpha03.txt
@@ -6,7 +6,6 @@
ctor @ContentView public ComponentActivity(@LayoutRes int);
method public androidx.lifecycle.ViewModelProvider.Factory getDefaultViewModelProviderFactory();
method @Deprecated public Object? getLastCustomNonConfigurationInstance();
- method public androidx.lifecycle.Lifecycle getLifecycle();
method public final androidx.activity.OnBackPressedDispatcher getOnBackPressedDispatcher();
method public final androidx.savedstate.SavedStateRegistry getSavedStateRegistry();
method public androidx.lifecycle.ViewModelStore getViewModelStore();
diff --git a/activity/activity/api/restricted_current.txt b/activity/activity/api/restricted_current.txt
index f087fd3..2a6b68b 100644
--- a/activity/activity/api/restricted_current.txt
+++ b/activity/activity/api/restricted_current.txt
@@ -6,7 +6,6 @@
ctor @ContentView public ComponentActivity(@LayoutRes int);
method public androidx.lifecycle.ViewModelProvider.Factory getDefaultViewModelProviderFactory();
method @Deprecated public Object? getLastCustomNonConfigurationInstance();
- method public androidx.lifecycle.Lifecycle getLifecycle();
method public final androidx.activity.OnBackPressedDispatcher getOnBackPressedDispatcher();
method public final androidx.savedstate.SavedStateRegistry getSavedStateRegistry();
method public androidx.lifecycle.ViewModelStore getViewModelStore();
diff --git a/activity/activity/build.gradle b/activity/activity/build.gradle
index b6edf7e..36959fc 100644
--- a/activity/activity/build.gradle
+++ b/activity/activity/build.gradle
@@ -18,7 +18,7 @@
dependencies {
api("androidx.annotation:annotation:1.1.0")
- api("androidx.core:core:1.1.0-rc01")
+ api("androidx.core:core:1.1.0")
api(project(":lifecycle:lifecycle-runtime"))
api(project(":lifecycle:lifecycle-viewmodel"))
api("androidx.savedstate:savedstate:1.0.0-rc01")
diff --git a/ads/ads-identifier-provider/build.gradle b/ads/ads-identifier-provider/build.gradle
index 78b4ade..535bbde 100644
--- a/ads/ads-identifier-provider/build.gradle
+++ b/ads/ads-identifier-provider/build.gradle
@@ -27,7 +27,7 @@
dependencies {
implementation("androidx.annotation:annotation:1.1.0")
- implementation("androidx.core:core:1.1.0-rc01")
+ implementation("androidx.core:core:1.1.0")
implementation(AUTO_VALUE_ANNOTATIONS)
annotationProcessor(AUTO_VALUE)
diff --git a/ads/ads-identifier/build.gradle b/ads/ads-identifier/build.gradle
index 3eca9f2..3ab81a6 100644
--- a/ads/ads-identifier/build.gradle
+++ b/ads/ads-identifier/build.gradle
@@ -27,7 +27,7 @@
dependencies {
implementation("androidx.annotation:annotation:1.1.0")
- implementation("androidx.core:core:1.1.0-rc01")
+ implementation("androidx.core:core:1.1.0")
implementation(AUTO_VALUE_ANNOTATIONS)
annotationProcessor(AUTO_VALUE)
api(GUAVA_LISTENABLE_FUTURE)
diff --git a/animation/build.gradle b/animation/build.gradle
index 65781d0..1d1800e 100644
--- a/animation/build.gradle
+++ b/animation/build.gradle
@@ -26,7 +26,7 @@
dependencies {
implementation("androidx.annotation:annotation:1.1.0")
- implementation("androidx.core:core:1.1.0-rc01")
+ implementation("androidx.core:core:1.1.0")
implementation("androidx.collection:collection:1.1.0")
androidTestImplementation(ANDROIDX_TEST_EXT_JUNIT, libs.exclude_for_espresso)
diff --git a/animation/integration-tests/testapp/build.gradle b/animation/integration-tests/testapp/build.gradle
index 82a6fe05..a9992a4 100644
--- a/animation/integration-tests/testapp/build.gradle
+++ b/animation/integration-tests/testapp/build.gradle
@@ -23,7 +23,7 @@
dependencies {
implementation("androidx.annotation:annotation:1.1.0")
- implementation("androidx.core:core:1.1.0-rc01")
+ implementation("androidx.core:core:1.1.0")
implementation(project(":animation"))
implementation(project(":animation:testing"))
diff --git a/animation/testing/build.gradle b/animation/testing/build.gradle
index f33191e..ad98a4c 100644
--- a/animation/testing/build.gradle
+++ b/animation/testing/build.gradle
@@ -26,7 +26,7 @@
dependencies {
implementation("androidx.annotation:annotation:1.1.0")
- implementation("androidx.core:core:1.1.0-rc01")
+ implementation("androidx.core:core:1.1.0")
implementation(project(":animation"))
implementation(ANDROIDX_TEST_EXT_JUNIT)
implementation(ANDROIDX_TEST_CORE)
diff --git a/appcompat/build.gradle b/appcompat/build.gradle
index 0f1a603..f9f3a5c 100644
--- a/appcompat/build.gradle
+++ b/appcompat/build.gradle
@@ -12,7 +12,7 @@
dependencies {
api("androidx.annotation:annotation:1.1.0")
- api("androidx.core:core:1.1.0-rc01")
+ api("androidx.core:core:1.1.0")
implementation("androidx.collection:collection:1.0.0")
api("androidx.cursoradapter:cursoradapter:1.0.0")
api("androidx.fragment:fragment:1.1.0-rc01")
diff --git a/appcompat/res/values-v23/styles_base.xml b/appcompat/res/values-v23/styles_base.xml
index f202cb8..d0ffcc3 100644
--- a/appcompat/res/values-v23/styles_base.xml
+++ b/appcompat/res/values-v23/styles_base.xml
@@ -19,7 +19,9 @@
<style name="Base.Widget.AppCompat.Button.Borderless.Colored" parent="android:Widget.Material.Button.Borderless.Colored" />
- <style name="Base.Widget.AppCompat.Button.Colored" parent="android:Widget.Material.Button.Colored" />
+ <style name="Base.Widget.AppCompat.Button.Colored" parent="android:Widget.Material.Button.Colored" >
+ <item name="android:textAppearance">@style/TextAppearance.AppCompat.Widget.Button.Colored</item>
+ </style>
<style name="Base.Widget.AppCompat.RatingBar.Indicator" parent="android:Widget.Material.RatingBar.Indicator" />
diff --git a/asynclayoutinflater/build.gradle b/asynclayoutinflater/build.gradle
index 2c12378..3dcd2f0 100644
--- a/asynclayoutinflater/build.gradle
+++ b/asynclayoutinflater/build.gradle
@@ -9,7 +9,7 @@
dependencies {
api("androidx.annotation:annotation:1.1.0")
- api("androidx.core:core:1.1.0-rc01")
+ api("androidx.core:core:1.1.0")
}
androidx {
diff --git a/benchmark/common/api/1.0.0-alpha05.txt b/benchmark/common/api/1.0.0-alpha05.txt
new file mode 100644
index 0000000..6849f29
--- /dev/null
+++ b/benchmark/common/api/1.0.0-alpha05.txt
@@ -0,0 +1,16 @@
+// Signature format: 3.0
+package androidx.benchmark {
+
+ public final class ArgumentsKt {
+ ctor public ArgumentsKt();
+ }
+
+ public final class BenchmarkState {
+ method public boolean keepRunning();
+ method public void pauseTiming();
+ method public static void reportData(String className, String testName, long totalRunTimeNs, java.util.List<java.lang.Long> dataNs, @IntRange(from=0) int warmupIterations, @IntRange(from=0) long thermalThrottleSleepSeconds, @IntRange(from=1) int repeatIterations);
+ method public void resumeTiming();
+ }
+
+}
+
diff --git a/preference/ktx/api/res-1.1.0-alpha05.txt b/benchmark/common/api/res-1.0.0-alpha05.txt
similarity index 100%
copy from preference/ktx/api/res-1.1.0-alpha05.txt
copy to benchmark/common/api/res-1.0.0-alpha05.txt
diff --git a/benchmark/common/api/restricted_1.0.0-alpha05.txt b/benchmark/common/api/restricted_1.0.0-alpha05.txt
new file mode 100644
index 0000000..4165e3e
--- /dev/null
+++ b/benchmark/common/api/restricted_1.0.0-alpha05.txt
@@ -0,0 +1,32 @@
+// Signature format: 3.0
+package androidx.benchmark {
+
+ public final class ArgumentsKt {
+ ctor public ArgumentsKt();
+ }
+
+ public final class BenchmarkState {
+ ctor @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP) public BenchmarkState();
+ method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP) public long getMin();
+ method public boolean keepRunning();
+ method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP) public inline boolean keepRunningInline();
+ method public void pauseTiming();
+ method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP) public void report(String fullClassName, String simpleClassName, String methodName);
+ method public static void reportData(String className, String testName, long totalRunTimeNs, java.util.List<java.lang.Long> dataNs, @IntRange(from=0) int warmupIterations, @IntRange(from=0) long thermalThrottleSleepSeconds, @IntRange(from=1) int repeatIterations);
+ method public void resumeTiming();
+ }
+
+ @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP) public final class IsolationActivity extends android.app.Activity {
+ method public void actuallyFinish();
+ field public static final androidx.benchmark.IsolationActivity.Companion! Companion;
+ }
+
+ public static final class IsolationActivity.Companion {
+ method @AnyThread public void finishSingleton();
+ method public boolean getResumed();
+ method @WorkerThread public void launchSingleton();
+ property public final boolean resumed;
+ }
+
+}
+
diff --git a/benchmark/common/src/main/java/androidx/benchmark/BenchmarkState.kt b/benchmark/common/src/main/java/androidx/benchmark/BenchmarkState.kt
index b59166e..c0e2a29 100644
--- a/benchmark/common/src/main/java/androidx/benchmark/BenchmarkState.kt
+++ b/benchmark/common/src/main/java/androidx/benchmark/BenchmarkState.kt
@@ -58,7 +58,7 @@
/** @hide */
@Suppress("ConvertSecondaryConstructorToPrimary")
@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
- constructor() {}
+ constructor()
private var warmupIteration = 0 // increasing iteration count during warmup
@@ -545,7 +545,7 @@
Errors.acquireWarningStringForLogging()?.split("\n") ?: listOf()
return (warningLines + ideSummaryLine(key, nanos))
// remove first line if empty
- .filterIndexed { index, it -> index != 0 || !it.isEmpty() }
+ .filterIndexed { index, it -> index != 0 || it.isNotEmpty() }
// join, prepending key to everything but first string,
// to make each line look the same
.joinToString("\n$STUDIO_OUTPUT_KEY_ID: ")
diff --git a/benchmark/common/src/main/java/androidx/benchmark/Errors.kt b/benchmark/common/src/main/java/androidx/benchmark/Errors.kt
index ce47af7..ea50a6d 100644
--- a/benchmark/common/src/main/java/androidx/benchmark/Errors.kt
+++ b/benchmark/common/src/main/java/androidx/benchmark/Errors.kt
@@ -159,7 +159,7 @@
| throttling. To fix this, add the following to your benchmark module-level
| build.gradle:
| android.defaultConfig.testInstrumentationRunner
- | = "androidx.benchmark.AndroidBenchmarkRunner"
+ | = "androidx.benchmark.junit4.AndroidBenchmarkRunner"
""".trimMarginWrapNewlines()
} else if (IsolationActivity.singleton.get() == null) {
warningPrefix += "ACTIVITY-MISSING_"
@@ -169,7 +169,7 @@
| from other visible apps. To fix this, add the following to your module-level
| build.gradle:
| android.defaultConfig.testInstrumentationRunner
- | = "androidx.benchmark.AndroidBenchmarkRunner"
+ | = "androidx.benchmark.junit4.AndroidBenchmarkRunner"
""".trimMarginWrapNewlines()
}
diff --git a/benchmark/gradle-plugin/src/main/kotlin/androidx/benchmark/gradle/Adb.kt b/benchmark/gradle-plugin/src/main/kotlin/androidx/benchmark/gradle/Adb.kt
index 0b39c10..2c25293 100644
--- a/benchmark/gradle-plugin/src/main/kotlin/androidx/benchmark/gradle/Adb.kt
+++ b/benchmark/gradle-plugin/src/main/kotlin/androidx/benchmark/gradle/Adb.kt
@@ -43,13 +43,26 @@
this.logger = logger
}
- fun isRooted(): Boolean {
+ /**
+ * Check if adb shell runs as root by default.
+ *
+ * The most common case for this is when adbd is running as root.
+ *
+ * @return `true` if adb shell runs as root by default, `false` otherwise.
+ */
+ fun isAdbdRoot(): Boolean {
val defaultUser = execSync("shell id").stdout
- if (defaultUser.contains("uid=0(root)")) {
- return true
- }
+ return defaultUser.contains("uid=0(root)")
+ }
- return execSync("shell su exit", shouldThrow = false).exitValue == 0
+ /**
+ * Check if the `su` binary is installed.
+ */
+ fun isSuInstalled(): Boolean {
+ // Not all devices / methods of rooting support su -c, but sh -c is usually supported.
+ // Although the root group is su's default, using syntax different from "su gid cmd", can
+ // cause the adb shell command to hang on some devices.
+ return execSync("shell su 0 sh -c exit", shouldThrow = false).exitValue == 0
}
fun execSync(
diff --git a/benchmark/gradle-plugin/src/main/kotlin/androidx/benchmark/gradle/LockClocksTask.kt b/benchmark/gradle-plugin/src/main/kotlin/androidx/benchmark/gradle/LockClocksTask.kt
index c03259a..e92ed60 100644
--- a/benchmark/gradle-plugin/src/main/kotlin/androidx/benchmark/gradle/LockClocksTask.kt
+++ b/benchmark/gradle-plugin/src/main/kotlin/androidx/benchmark/gradle/LockClocksTask.kt
@@ -17,6 +17,7 @@
package androidx.benchmark.gradle
import org.gradle.api.DefaultTask
+import org.gradle.api.GradleException
import org.gradle.api.provider.Property
import org.gradle.api.tasks.Input
import org.gradle.api.tasks.TaskAction
@@ -39,9 +40,13 @@
fun exec() {
val adb = Adb(adbPath.get(), logger)
- // Skip "adb root" if already rooted as it will fail.
- if (adb.isRooted()) {
- adb.execSync("root", silent = true)
+ adb.execSync("root", silent = true, shouldThrow = false)
+
+ val isAdbdRoot = adb.isAdbdRoot()
+ val isRooted = isAdbdRoot || adb.isSuInstalled()
+
+ if (!isRooted) {
+ throw GradleException("Your device must be rooted to lock clocks.")
}
val dest = "/data/local/tmp/lockClocks.sh"
@@ -56,7 +61,14 @@
// Files pushed by adb push don't always preserve file permissions.
adb.execSync("shell chmod 700 $dest")
- adb.execSync("shell $dest")
+ if (!isAdbdRoot) {
+ // Default shell is not running as root, escalate with su 0. Although the root group is
+ // su's default, using syntax different from "su gid cmd", can cause the adb shell
+ // command to hang on some devices.
+ adb.execSync("shell su 0 $dest")
+ } else {
+ adb.execSync("shell $dest")
+ }
adb.execSync("shell rm $dest")
}
}
diff --git a/benchmark/junit4/api/1.0.0-alpha05.txt b/benchmark/junit4/api/1.0.0-alpha05.txt
new file mode 100644
index 0000000..756b010
--- /dev/null
+++ b/benchmark/junit4/api/1.0.0-alpha05.txt
@@ -0,0 +1,24 @@
+// Signature format: 3.0
+package androidx.benchmark.junit4 {
+
+ public class AndroidBenchmarkRunner extends androidx.test.runner.AndroidJUnitRunner {
+ ctor public AndroidBenchmarkRunner();
+ }
+
+ public final class BenchmarkRule implements org.junit.rules.TestRule {
+ ctor public BenchmarkRule();
+ method public org.junit.runners.model.Statement apply(org.junit.runners.model.Statement base, org.junit.runner.Description description);
+ method public androidx.benchmark.BenchmarkState getState();
+ }
+
+ public final class BenchmarkRule.Scope {
+ method public inline <T> T! runWithTimingDisabled(kotlin.jvm.functions.Function0<? extends T> block);
+ }
+
+ public final class BenchmarkRuleKt {
+ ctor public BenchmarkRuleKt();
+ method public static inline void measureRepeated(androidx.benchmark.junit4.BenchmarkRule, kotlin.jvm.functions.Function1<? super androidx.benchmark.junit4.BenchmarkRule.Scope,kotlin.Unit> block);
+ }
+
+}
+
diff --git a/preference/ktx/api/res-1.1.0-alpha05.txt b/benchmark/junit4/api/res-1.0.0-alpha05.txt
similarity index 100%
copy from preference/ktx/api/res-1.1.0-alpha05.txt
copy to benchmark/junit4/api/res-1.0.0-alpha05.txt
diff --git a/benchmark/junit4/api/restricted_1.0.0-alpha05.txt b/benchmark/junit4/api/restricted_1.0.0-alpha05.txt
new file mode 100644
index 0000000..756b010
--- /dev/null
+++ b/benchmark/junit4/api/restricted_1.0.0-alpha05.txt
@@ -0,0 +1,24 @@
+// Signature format: 3.0
+package androidx.benchmark.junit4 {
+
+ public class AndroidBenchmarkRunner extends androidx.test.runner.AndroidJUnitRunner {
+ ctor public AndroidBenchmarkRunner();
+ }
+
+ public final class BenchmarkRule implements org.junit.rules.TestRule {
+ ctor public BenchmarkRule();
+ method public org.junit.runners.model.Statement apply(org.junit.runners.model.Statement base, org.junit.runner.Description description);
+ method public androidx.benchmark.BenchmarkState getState();
+ }
+
+ public final class BenchmarkRule.Scope {
+ method public inline <T> T! runWithTimingDisabled(kotlin.jvm.functions.Function0<? extends T> block);
+ }
+
+ public final class BenchmarkRuleKt {
+ ctor public BenchmarkRuleKt();
+ method public static inline void measureRepeated(androidx.benchmark.junit4.BenchmarkRule, kotlin.jvm.functions.Function1<? super androidx.benchmark.junit4.BenchmarkRule.Scope,kotlin.Unit> block);
+ }
+
+}
+
diff --git a/benchmark/junit4/src/main/java/androidx/benchmark/junit4/AndroidBenchmarkRunner.kt b/benchmark/junit4/src/main/java/androidx/benchmark/junit4/AndroidBenchmarkRunner.kt
index 1ca90b3..ef63614 100644
--- a/benchmark/junit4/src/main/java/androidx/benchmark/junit4/AndroidBenchmarkRunner.kt
+++ b/benchmark/junit4/src/main/java/androidx/benchmark/junit4/AndroidBenchmarkRunner.kt
@@ -29,7 +29,7 @@
* ```
* android {
* defaultConfig {
- * testInstrumentationRunner "androidx.benchmark.AndroidBenchmarkRunner"
+ * testInstrumentationRunner "androidx.benchmark.junit4.AndroidBenchmarkRunner"
* }
* }
* ```
diff --git a/biometric/api/1.0.0-beta01.txt b/biometric/api/1.0.0-alpha06.txt
similarity index 96%
rename from biometric/api/1.0.0-beta01.txt
rename to biometric/api/1.0.0-alpha06.txt
index 63a2dfc..b17b443 100644
--- a/biometric/api/1.0.0-beta01.txt
+++ b/biometric/api/1.0.0-alpha06.txt
@@ -65,7 +65,7 @@
method public androidx.biometric.BiometricPrompt.PromptInfo build();
method public androidx.biometric.BiometricPrompt.PromptInfo.Builder setConfirmationRequired(boolean);
method public androidx.biometric.BiometricPrompt.PromptInfo.Builder setDescription(CharSequence?);
- method @RequiresApi(29) public androidx.biometric.BiometricPrompt.PromptInfo.Builder setDeviceCredentialAllowed(boolean);
+ method public androidx.biometric.BiometricPrompt.PromptInfo.Builder setDeviceCredentialAllowed(boolean);
method public androidx.biometric.BiometricPrompt.PromptInfo.Builder setNegativeButtonText(CharSequence);
method public androidx.biometric.BiometricPrompt.PromptInfo.Builder setSubtitle(CharSequence?);
method public androidx.biometric.BiometricPrompt.PromptInfo.Builder setTitle(CharSequence);
diff --git a/biometric/api/current.txt b/biometric/api/current.txt
index 63a2dfc..b17b443 100644
--- a/biometric/api/current.txt
+++ b/biometric/api/current.txt
@@ -65,7 +65,7 @@
method public androidx.biometric.BiometricPrompt.PromptInfo build();
method public androidx.biometric.BiometricPrompt.PromptInfo.Builder setConfirmationRequired(boolean);
method public androidx.biometric.BiometricPrompt.PromptInfo.Builder setDescription(CharSequence?);
- method @RequiresApi(29) public androidx.biometric.BiometricPrompt.PromptInfo.Builder setDeviceCredentialAllowed(boolean);
+ method public androidx.biometric.BiometricPrompt.PromptInfo.Builder setDeviceCredentialAllowed(boolean);
method public androidx.biometric.BiometricPrompt.PromptInfo.Builder setNegativeButtonText(CharSequence);
method public androidx.biometric.BiometricPrompt.PromptInfo.Builder setSubtitle(CharSequence?);
method public androidx.biometric.BiometricPrompt.PromptInfo.Builder setTitle(CharSequence);
diff --git a/biometric/api/res-1.0.0-beta01.txt b/biometric/api/res-1.0.0-alpha06.txt
similarity index 100%
rename from biometric/api/res-1.0.0-beta01.txt
rename to biometric/api/res-1.0.0-alpha06.txt
diff --git a/biometric/api/restricted_1.0.0-beta01.txt b/biometric/api/restricted_1.0.0-alpha06.txt
similarity index 95%
rename from biometric/api/restricted_1.0.0-beta01.txt
rename to biometric/api/restricted_1.0.0-alpha06.txt
index 6e7da51..cf9f995 100644
--- a/biometric/api/restricted_1.0.0-beta01.txt
+++ b/biometric/api/restricted_1.0.0-alpha06.txt
@@ -54,7 +54,7 @@
method public androidx.biometric.BiometricPrompt.PromptInfo build();
method public androidx.biometric.BiometricPrompt.PromptInfo.Builder setConfirmationRequired(boolean);
method public androidx.biometric.BiometricPrompt.PromptInfo.Builder setDescription(CharSequence?);
- method @RequiresApi(29) public androidx.biometric.BiometricPrompt.PromptInfo.Builder setDeviceCredentialAllowed(boolean);
+ method public androidx.biometric.BiometricPrompt.PromptInfo.Builder setDeviceCredentialAllowed(boolean);
method public androidx.biometric.BiometricPrompt.PromptInfo.Builder setNegativeButtonText(CharSequence);
method public androidx.biometric.BiometricPrompt.PromptInfo.Builder setSubtitle(CharSequence?);
method public androidx.biometric.BiometricPrompt.PromptInfo.Builder setTitle(CharSequence);
@@ -63,5 +63,6 @@
+
}
diff --git a/biometric/api/restricted_current.txt b/biometric/api/restricted_current.txt
index 6e7da51..cf9f995 100644
--- a/biometric/api/restricted_current.txt
+++ b/biometric/api/restricted_current.txt
@@ -54,7 +54,7 @@
method public androidx.biometric.BiometricPrompt.PromptInfo build();
method public androidx.biometric.BiometricPrompt.PromptInfo.Builder setConfirmationRequired(boolean);
method public androidx.biometric.BiometricPrompt.PromptInfo.Builder setDescription(CharSequence?);
- method @RequiresApi(29) public androidx.biometric.BiometricPrompt.PromptInfo.Builder setDeviceCredentialAllowed(boolean);
+ method public androidx.biometric.BiometricPrompt.PromptInfo.Builder setDeviceCredentialAllowed(boolean);
method public androidx.biometric.BiometricPrompt.PromptInfo.Builder setNegativeButtonText(CharSequence);
method public androidx.biometric.BiometricPrompt.PromptInfo.Builder setSubtitle(CharSequence?);
method public androidx.biometric.BiometricPrompt.PromptInfo.Builder setTitle(CharSequence);
@@ -63,5 +63,6 @@
+
}
diff --git a/biometric/build.gradle b/biometric/build.gradle
index e423137..e1dbcfe 100644
--- a/biometric/build.gradle
+++ b/biometric/build.gradle
@@ -10,7 +10,7 @@
dependencies {
api("androidx.annotation:annotation:1.1.0")
api("androidx.appcompat:appcompat:1.1.0-rc01")
- api("androidx.core:core:1.1.0-rc02")
+ api("androidx.core:core:1.1.0")
api("androidx.fragment:fragment:1.1.0-rc01")
}
diff --git a/biometric/res/layout/device_credential_handler_activity.xml b/biometric/res/layout/device_credential_handler_activity.xml
new file mode 100644
index 0000000..5708761
--- /dev/null
+++ b/biometric/res/layout/device_credential_handler_activity.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ 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.
+ -->
+
+<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent" android:layout_height="match_parent">
+
+</FrameLayout>
\ No newline at end of file
diff --git a/biometric/res/values/strings.xml b/biometric/res/values/strings.xml
index 1ee1534..04516c6 100644
--- a/biometric/res/values/strings.xml
+++ b/biometric/res/values/strings.xml
@@ -36,4 +36,10 @@
<string name="fingerprint_error_lockout">Too many attempts. Please try again later.</string>
<!-- Generic error message shown when an unknown error has occurred. [CHAR LIMIT=NONE] -->
<string name="default_error_msg">Unknown error</string>
+ <!-- Generic error message shown when the authentication operation is canceled due to user
+ input. Generally not shown to the user. [CHAR LIMIT=NONE] -->
+ <string name="generic_error_user_canceled">Authentication canceled by user.</string>
+ <!-- Button label shown on a biometric authentication system dialog that lets the user
+ authenticate using their lock screen credential (such as PIN or password). [CHAR LIMIT=30] -->
+ <string name="confirm_device_credential_password">Use password</string>
</resources>
diff --git a/biometric/res/values/styles.xml b/biometric/res/values/styles.xml
new file mode 100644
index 0000000..fce6eb0
--- /dev/null
+++ b/biometric/res/values/styles.xml
@@ -0,0 +1,36 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ 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.
+ -->
+
+<resources>
+ <style name="DeviceCredentialHandlerTheme" parent="Theme.AppCompat">
+ <item name="android:windowIsTranslucent">true</item>
+ <item name="android:windowBackground">@android:color/transparent</item>
+ <item name="android:windowContentOverlay">@null</item>
+ <item name="android:windowNoTitle">true</item>
+ <item name="android:windowIsFloating">true</item>
+ <item name="android:backgroundDimEnabled">false</item>
+ </style>
+
+ <style name="TransparentStyle">
+ <item name="android:windowIsTranslucent">true</item>
+ <item name="android:windowBackground">@android:color/transparent</item>
+ <item name="android:windowContentOverlay">@null</item>
+ <item name="android:windowNoTitle">true</item>
+ <item name="android:windowIsFloating">true</item>
+ <item name="android:backgroundDimEnabled">false</item>
+ </style>
+</resources>
\ No newline at end of file
diff --git a/biometric/src/main/AndroidManifest.xml b/biometric/src/main/AndroidManifest.xml
index af0a0328..8ff1f77 100644
--- a/biometric/src/main/AndroidManifest.xml
+++ b/biometric/src/main/AndroidManifest.xml
@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2014 The Android Open Source Project
+<!-- Copyright (C) 2018 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.
@@ -13,8 +13,17 @@
See the License for the specific language governing permissions and
limitations under the License.
-->
-<manifest package="androidx.biometric"
- xmlns:android="http://schemas.android.com/apk/res/android">
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="androidx.biometric">
+
<uses-permission android:name="android.permission.USE_BIOMETRIC" />
+
<uses-permission android:name="android.permission.USE_FINGERPRINT" />
-</manifest>
+
+ <application>
+ <activity
+ android:name=".DeviceCredentialHandlerActivity"
+ android:theme="@style/DeviceCredentialHandlerTheme" />
+ </application>
+
+</manifest>
\ No newline at end of file
diff --git a/biometric/src/main/java/androidx/biometric/BiometricFragment.java b/biometric/src/main/java/androidx/biometric/BiometricFragment.java
index 941161b..26a95601 100644
--- a/biometric/src/main/java/androidx/biometric/BiometricFragment.java
+++ b/biometric/src/main/java/androidx/biometric/BiometricFragment.java
@@ -17,8 +17,10 @@
package androidx.biometric;
import android.annotation.SuppressLint;
+import android.app.KeyguardManager;
import android.content.Context;
import android.content.DialogInterface;
+import android.content.Intent;
import android.os.Build;
import android.os.Bundle;
import android.os.CancellationSignal;
@@ -35,6 +37,7 @@
import androidx.annotation.RequiresApi;
import androidx.annotation.RestrictTo;
import androidx.fragment.app.Fragment;
+import androidx.fragment.app.FragmentActivity;
import java.util.concurrent.Executor;
@@ -43,10 +46,11 @@
* device configuration changes. This class is not meant to be preserved after process death; for
* security reasons, the BiometricPromptCompat will automatically stop authentication when the
* activity is no longer in the foreground.
+ *
* @hide
*/
@RestrictTo(RestrictTo.Scope.LIBRARY)
-@RequiresApi(28)
+@RequiresApi(Build.VERSION_CODES.P)
@SuppressLint("SyntheticAccessor")
public class BiometricFragment extends Fragment {
@@ -75,12 +79,7 @@
// Do not rely on the application's executor when calling into the framework's code.
private final Handler mHandler = new Handler(Looper.getMainLooper());
- private final Executor mExecutor = new Executor() {
- @Override
- public void execute(Runnable runnable) {
- mHandler.post(runnable);
- }
- };
+ private final Executor mExecutor = mHandler::post;
// Also created once and retained.
private final android.hardware.biometrics.BiometricPrompt.AuthenticationCallback
@@ -89,18 +88,15 @@
@Override
public void onAuthenticationError(final int errorCode,
final CharSequence errString) {
- mClientExecutor.execute(new Runnable() {
- @Override
- public void run() {
- CharSequence error = errString;
- if (error == null) {
- error = mContext.getString(R.string.default_error_msg) + " "
- + errorCode;
- }
- mClientAuthenticationCallback
- .onAuthenticationError(Utils.isUnknownError(errorCode)
- ? BiometricPrompt.ERROR_VENDOR : errorCode, error);
+ mClientExecutor.execute(() -> {
+ CharSequence error = errString;
+ if (error == null) {
+ error = mContext.getString(R.string.default_error_msg) + " "
+ + errorCode;
}
+ mClientAuthenticationCallback
+ .onAuthenticationError(Utils.isUnknownError(errorCode)
+ ? BiometricPrompt.ERROR_VENDOR : errorCode, error);
});
cleanup();
}
@@ -115,30 +111,21 @@
public void onAuthenticationSucceeded(
final android.hardware.biometrics.BiometricPrompt.AuthenticationResult
result) {
- mClientExecutor.execute(new Runnable() {
- @Override
- public void run() {
- mClientAuthenticationCallback.onAuthenticationSucceeded(
+ mClientExecutor.execute(
+ () -> mClientAuthenticationCallback.onAuthenticationSucceeded(
new BiometricPrompt.AuthenticationResult(
- unwrapCryptoObject(result.getCryptoObject())));
- }
- });
+ unwrapCryptoObject(result.getCryptoObject()))));
cleanup();
}
@Override
public void onAuthenticationFailed() {
- mClientExecutor.execute(new Runnable() {
- @Override
- public void run() {
- mClientAuthenticationCallback.onAuthenticationFailed();
- }
- });
+ mClientExecutor.execute(mClientAuthenticationCallback::onAuthenticationFailed);
}
};
// Also created once and retained.
- private DialogInterface.OnClickListener mNegativeButtonListener =
+ private final DialogInterface.OnClickListener mNegativeButtonListener =
new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
@@ -146,23 +133,61 @@
}
};
+ // Also created once and retained.
+ @SuppressWarnings("deprecation")
+ private final DialogInterface.OnClickListener mDeviceCredentialButtonListener =
+ (dialog, which) -> {
+ if (which == DialogInterface.BUTTON_NEGATIVE) {
+ final FragmentActivity activity = getActivity();
+ if (!(activity instanceof DeviceCredentialHandlerActivity)) {
+ Log.e(TAG, "Failed to check device credential. Parent handler not found.");
+ return;
+ }
+
+ final KeyguardManager km = activity.getSystemService(KeyguardManager.class);
+ if (km == null) {
+ Log.e(TAG, "Failed to check device credential. KeyguardManager was null.");
+ return;
+ }
+
+ // Pass along the title and subtitle from the biometric prompt.
+ final CharSequence title;
+ final CharSequence subtitle;
+ if (mBundle != null) {
+ title = mBundle.getCharSequence(BiometricPrompt.KEY_TITLE);
+ subtitle = mBundle.getCharSequence(BiometricPrompt.KEY_SUBTITLE);
+ } else {
+ title = null;
+ subtitle = null;
+ }
+
+ // Prevent the bridge from resetting until the confirmation activity finishes.
+ DeviceCredentialHandlerBridge bridge =
+ DeviceCredentialHandlerBridge.getInstanceIfNotNull();
+ if (bridge != null) {
+ bridge.startIgnoringReset();
+ }
+
+ // Launch a new instance of the confirm device credential Settings activity.
+ final Intent intent = km.createConfirmDeviceCredentialIntent(title, subtitle);
+ intent.setFlags(
+ Intent.FLAG_ACTIVITY_MULTIPLE_TASK | Intent.FLAG_ACTIVITY_NEW_DOCUMENT);
+ activity.startActivityForResult(intent, 0 /* requestCode */);
+ }
+ };
+
/**
* Creates a new instance of the {@link BiometricFragment}.
- * @return
*/
- public static BiometricFragment newInstance() {
- BiometricFragment biometricFragment = new BiometricFragment();
- return biometricFragment;
+ static BiometricFragment newInstance() {
+ return new BiometricFragment();
}
/**
* Sets the client's callback. This should be done whenever the lifecycle changes (orientation
* changes).
- * @param executor
- * @param onClickListener
- * @param authenticationCallback
*/
- protected void setCallbacks(Executor executor, DialogInterface.OnClickListener onClickListener,
+ void setCallbacks(Executor executor, DialogInterface.OnClickListener onClickListener,
BiometricPrompt.AuthenticationCallback authenticationCallback) {
mClientExecutor = executor;
mClientNegativeButtonListener = onClickListener;
@@ -172,16 +197,15 @@
/**
* Sets the crypto object to be associated with the authentication. Should be called before
* adding the fragment to guarantee that it's ready in onCreate().
- * @param crypto
*/
- protected void setCryptoObject(BiometricPrompt.CryptoObject crypto) {
+ void setCryptoObject(BiometricPrompt.CryptoObject crypto) {
mCryptoObject = crypto;
}
/**
* Cancel the authentication.
*/
- protected void cancel() {
+ void cancel() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q && isDeviceCredentialAllowed()) {
if (!mStartRespectingCancel) {
Log.w(TAG, "Ignoring fast cancel signal");
@@ -199,11 +223,14 @@
*/
void cleanup() {
mShowing = false;
+ FragmentActivity activity = getActivity();
if (getFragmentManager() != null) {
getFragmentManager().beginTransaction().detach(this).commitAllowingStateLoss();
}
+ Utils.maybeFinishHandler(activity);
}
+ @Nullable
protected CharSequence getNegativeButtonText() {
return mNegativeButtonText;
}
@@ -218,12 +245,12 @@
mBundle = bundle;
}
- public boolean isDeviceCredentialAllowed() {
+ boolean isDeviceCredentialAllowed() {
return mBundle.getBoolean(BiometricPrompt.KEY_ALLOW_DEVICE_CREDENTIAL, false);
}
@Override
- public void onAttach(Context context) {
+ public void onAttach(@NonNull Context context) {
super.onAttach(context);
mContext = context;
}
@@ -234,37 +261,40 @@
// Start the actual authentication when the fragment is attached.
if (!mShowing) {
mNegativeButtonText = mBundle.getCharSequence(BiometricPrompt.KEY_NEGATIVE_TEXT);
+
final android.hardware.biometrics.BiometricPrompt.Builder builder =
new android.hardware.biometrics.BiometricPrompt.Builder(getContext());
builder.setTitle(mBundle.getCharSequence(BiometricPrompt.KEY_TITLE))
.setSubtitle(mBundle.getCharSequence(BiometricPrompt.KEY_SUBTITLE))
.setDescription(mBundle.getCharSequence(BiometricPrompt.KEY_DESCRIPTION));
- // The negative text could be empty if setDeviceCredentialAllowed is true.
- if (!TextUtils.isEmpty(mBundle.getCharSequence(BiometricPrompt.KEY_NEGATIVE_TEXT))) {
+
+ final boolean allowDeviceCredential =
+ mBundle.getBoolean(BiometricPrompt.KEY_ALLOW_DEVICE_CREDENTIAL);
+
+ // Provide our own negative button text if allowing device credential on <= P.
+ if (allowDeviceCredential && Build.VERSION.SDK_INT <= Build.VERSION_CODES.P) {
+ mNegativeButtonText = getString(R.string.confirm_device_credential_password);
builder.setNegativeButton(
- mBundle.getCharSequence(BiometricPrompt.KEY_NEGATIVE_TEXT),
- mClientExecutor, mNegativeButtonListener);
+ mNegativeButtonText, mClientExecutor, mDeviceCredentialButtonListener);
+ } else if (!TextUtils.isEmpty(mNegativeButtonText)) {
+ builder.setNegativeButton(
+ mNegativeButtonText, mClientExecutor, mNegativeButtonListener);
}
+ // Set builder flags introduced in Q.
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
builder.setConfirmationRequired(
mBundle.getBoolean((BiometricPrompt.KEY_REQUIRE_CONFIRMATION), true));
- builder.setDeviceCredentialAllowed(
- mBundle.getBoolean(BiometricPrompt.KEY_ALLOW_DEVICE_CREDENTIAL));
+ builder.setDeviceCredentialAllowed(allowDeviceCredential);
}
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
- if (mBundle.getBoolean(BiometricPrompt.KEY_ALLOW_DEVICE_CREDENTIAL, false)) {
- mStartRespectingCancel = false;
- mHandler.postDelayed(new Runnable() {
- @Override
- public void run() {
- // Hack almost over 9000, ignore cancel signal in Q if it's within the
- // first quarter second.
- mStartRespectingCancel = true;
- }
- }, 250 /* ms */);
- }
+ if (allowDeviceCredential) {
+ mStartRespectingCancel = false;
+ mHandler.postDelayed(() -> {
+ // Hack almost over 9000, ignore cancel signal if it's within the first quarter
+ // second.
+ mStartRespectingCancel = true;
+ }, 250 /* ms */);
}
mBiometricPrompt = builder.build();
@@ -281,7 +311,7 @@
return super.onCreateView(inflater, container, savedInstanceState);
}
- static BiometricPrompt.CryptoObject unwrapCryptoObject(
+ private static BiometricPrompt.CryptoObject unwrapCryptoObject(
android.hardware.biometrics.BiometricPrompt.CryptoObject cryptoObject) {
if (cryptoObject == null) {
return null;
@@ -296,7 +326,7 @@
}
}
- static android.hardware.biometrics.BiometricPrompt.CryptoObject wrapCryptoObject(
+ private static android.hardware.biometrics.BiometricPrompt.CryptoObject wrapCryptoObject(
BiometricPrompt.CryptoObject cryptoObject) {
if (cryptoObject == null) {
return null;
diff --git a/biometric/src/main/java/androidx/biometric/BiometricPrompt.java b/biometric/src/main/java/androidx/biometric/BiometricPrompt.java
index cd000b0..eb98834 100644
--- a/biometric/src/main/java/androidx/biometric/BiometricPrompt.java
+++ b/biometric/src/main/java/androidx/biometric/BiometricPrompt.java
@@ -20,6 +20,8 @@
import android.annotation.SuppressLint;
import android.content.DialogInterface;
+import android.content.Intent;
+import android.content.pm.PackageManager;
import android.os.Build;
import android.os.Bundle;
import android.os.Handler;
@@ -29,7 +31,7 @@
import androidx.annotation.IntDef;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
-import androidx.annotation.RequiresApi;
+import androidx.annotation.RestrictTo;
import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentActivity;
import androidx.fragment.app.FragmentManager;
@@ -81,6 +83,7 @@
static final String KEY_NEGATIVE_TEXT = "negative_text";
static final String KEY_REQUIRE_CONFIRMATION = "require_confirmation";
static final String KEY_ALLOW_DEVICE_CREDENTIAL = "allow_device_credential";
+ static final String KEY_HANDLING_DEVICE_CREDENTIAL_RESULT = "handling_device_credential_result";
@Retention(SOURCE)
@IntDef({ERROR_HW_UNAVAILABLE,
@@ -96,7 +99,8 @@
ERROR_HW_NOT_PRESENT,
ERROR_NEGATIVE_BUTTON,
ERROR_NO_DEVICE_CREDENTIAL})
- private @interface BiometricError {}
+ private @interface BiometricError {
+ }
/**
* A wrapper class for the crypto objects supported by BiometricPrompt. Currently the
@@ -127,6 +131,7 @@
/**
* Get {@link Signature} object.
+ *
* @return {@link Signature} object or null if this doesn't contain one.
*/
@Nullable
@@ -136,6 +141,7 @@
/**
* Get {@link Cipher} object.
+ *
* @return {@link Cipher} object or null if this doesn't contain one.
*/
@Nullable
@@ -145,6 +151,7 @@
/**
* Get {@link Mac} object.
+ *
* @return {@link Mac} object or null if this doesn't contain one.
*/
@Nullable
@@ -161,7 +168,7 @@
private final CryptoObject mCryptoObject;
/**
- * @param crypto
+ *
*/
AuthenticationResult(CryptoObject crypto) {
mCryptoObject = crypto;
@@ -169,6 +176,7 @@
/**
* Obtain the crypto object associated with this transaction
+ *
* @return crypto object provided to {@link #authenticate(PromptInfo, CryptoObject)}.
*/
@Nullable
@@ -186,24 +194,29 @@
/**
* Called when an unrecoverable error has been encountered and the operation is complete.
* No further actions will be made on this object.
+ *
* @param errorCode An integer identifying the error message. The error message will usually
* be one of the BIOMETRIC_ERROR constants.
* @param errString A human-readable error string that can be shown on an UI
*/
public void onAuthenticationError(@BiometricError int errorCode,
- @NonNull CharSequence errString) {}
+ @NonNull CharSequence errString) {
+ }
/**
* Called when a biometric is recognized.
+ *
* @param result An object containing authentication-related data
*/
- public void onAuthenticationSucceeded(@NonNull AuthenticationResult result) {}
+ public void onAuthenticationSucceeded(@NonNull AuthenticationResult result) {
+ }
/**
* Called when a biometric is valid but not recognized.
*/
- public void onAuthenticationFailed() {}
+ public void onAuthenticationFailed() {
+ }
}
/**
@@ -249,8 +262,6 @@
* Required: Set the text for the negative button. This would typically be used as a
* "Cancel" button, but may be also used to show an alternative method for
* authentication, such as screen that asks for a backup password.
- * @param text
- * @return
*/
@NonNull
public Builder setNegativeButtonText(@NonNull CharSequence text) {
@@ -288,16 +299,19 @@
* first check {@link android.app.KeyguardManager#isDeviceSecure()} before enabling
* this. If the device is not secure, {@link BiometricPrompt#ERROR_NO_DEVICE_CREDENTIAL}
* will be returned in
- * {@link AuthenticationCallback#onAuthenticationError(int, CharSequence)}}
+ * {@link AuthenticationCallback#onAuthenticationError(int, CharSequence)}.
*
* Note that {@link Builder#setNegativeButtonText(CharSequence)} should not be set
* if this is set to true.
*
+ * On versions P and below, once the device credential prompt is shown,
+ * {@link #cancelAuthentication()} will not work, since the library internally launches
+ * {@link android.app.KeyguardManager#createConfirmDeviceCredentialIntent(CharSequence,
+ * CharSequence)}, which does not have a public API for cancellation.
+ *
* @param enable When true, the prompt will fall back to ask for the user's device
* credentials (PIN, pattern, or password).
- * @return
*/
- @RequiresApi(29)
@NonNull
public Builder setDeviceCredentialAllowed(boolean enable) {
mBundle.putBoolean(KEY_ALLOW_DEVICE_CREDENTIAL, enable);
@@ -305,7 +319,23 @@
}
/**
+ * A flag that is set to true when launching the prompt within the transparent
+ * {@link DeviceCredentialHandlerActivity}. This lets us handle the result of {@link
+ * android.app.KeyguardManager#createConfirmDeviceCredentialIntent(CharSequence,
+ * CharSequence)} in order to allow device credentials for <= P.
+ *
+ * @hide
+ */
+ @RestrictTo(RestrictTo.Scope.LIBRARY)
+ @NonNull
+ Builder setHandlingDeviceCredentialResult(boolean isHandling) {
+ mBundle.putBoolean(KEY_HANDLING_DEVICE_CREDENTIAL_RESULT, isHandling);
+ return this;
+ }
+
+ /**
* Creates a {@link BiometricPrompt}.
+ *
* @return a {@link BiometricPrompt}
* @throws IllegalArgumentException if any of the required fields are not set.
*/
@@ -314,6 +344,8 @@
final CharSequence title = mBundle.getCharSequence(KEY_TITLE);
final CharSequence negative = mBundle.getCharSequence(KEY_NEGATIVE_TEXT);
boolean allowDeviceCredential = mBundle.getBoolean(KEY_ALLOW_DEVICE_CREDENTIAL);
+ boolean handlingDeviceCredentialResult =
+ mBundle.getBoolean(KEY_HANDLING_DEVICE_CREDENTIAL_RESULT);
if (TextUtils.isEmpty(title)) {
throw new IllegalArgumentException("Title must be set and non-empty");
@@ -325,6 +357,10 @@
throw new IllegalArgumentException("Can't have both negative button behavior"
+ " and device credential enabled");
}
+ if (handlingDeviceCredentialResult && !allowDeviceCredential) {
+ throw new IllegalArgumentException("Can't be handling device credential result"
+ + " without device credential enabled");
+ }
return new PromptInfo(mBundle);
}
}
@@ -384,57 +420,63 @@
public boolean isDeviceCredentialAllowed() {
return mBundle.getBoolean(KEY_ALLOW_DEVICE_CREDENTIAL);
}
+
+ /**
+ * @return See {@link Builder#setHandlingDeviceCredentialResult(boolean)}.
+ *
+ * @hide
+ */
+ @RestrictTo(RestrictTo.Scope.LIBRARY)
+ boolean isHandlingDeviceCredentialResult() {
+ return mBundle.getBoolean(KEY_HANDLING_DEVICE_CREDENTIAL_RESULT);
+ }
}
// Passed in from the client.
- FragmentActivity mFragmentActivity;
- Fragment mFragment;
- final Executor mExecutor;
- final AuthenticationCallback mAuthenticationCallback;
+ private FragmentActivity mFragmentActivity;
+ private Fragment mFragment;
+ private final Executor mExecutor;
+ private final AuthenticationCallback mAuthenticationCallback;
// Created internally for devices before P.
- FingerprintDialogFragment mFingerprintDialogFragment;
- FingerprintHelperFragment mFingerprintHelperFragment;
+ private FingerprintDialogFragment mFingerprintDialogFragment;
+ private FingerprintHelperFragment mFingerprintHelperFragment;
// Created internally for devices P and above.
- BiometricFragment mBiometricFragment;
+ private BiometricFragment mBiometricFragment;
// In Q, we must ignore the first onPause if setDeviceCredentialAllowed is true, since
// the Q implementation launches ConfirmDeviceCredentialActivity which is an activity and
// puts the client app onPause.
- boolean mPausedOnce;
+ private boolean mPausedOnce;
+
+ // Whether this prompt is being hosted in DeviceCredentialHandlerActivity.
+ private boolean mIsHandlingDeviceCredential;
/**
- * A shim to interface with the framework API and simplify the support library's API.
- * The support library sends onAuthenticationError when the negative button is pressed.
- * Conveniently, the {@link FingerprintDialogFragment} also uses the
- * {@DialogInterface.OnClickListener} for its buttons ;)
+ * A shim to interface with the framework API and simplify the support library's API.
+ * The support library sends onAuthenticationError when the negative button is pressed.
+ * Conveniently, the {@link FingerprintDialogFragment} also uses the
+ * {@link DialogInterface.OnClickListener} for its buttons ;)
*/
- final DialogInterface.OnClickListener mNegativeButtonListener =
+ private final DialogInterface.OnClickListener mNegativeButtonListener =
new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
- mExecutor.execute(new Runnable() {
- @Override
- public void run() {
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P
- && !DEBUG_FORCE_FINGERPRINT) {
- CharSequence errorText =
- mBiometricFragment.getNegativeButtonText();
- mAuthenticationCallback.onAuthenticationError(
- ERROR_NEGATIVE_BUTTON,
- errorText);
- mBiometricFragment.cleanup();
- } else {
- CharSequence errorText =
- mFingerprintDialogFragment.getNegativeButtonText();
- mAuthenticationCallback.onAuthenticationError(
- ERROR_NEGATIVE_BUTTON,
- errorText);
- mFingerprintHelperFragment.cancel(
- FingerprintHelperFragment
- .USER_CANCELED_FROM_NEGATIVE_BUTTON);
- }
+ mExecutor.execute(() -> {
+ if (usingBiometricFragment()) {
+ final CharSequence errorText =
+ mBiometricFragment.getNegativeButtonText();
+ mAuthenticationCallback.onAuthenticationError(
+ ERROR_NEGATIVE_BUTTON, errorText != null ? errorText : "");
+ mBiometricFragment.cleanup();
+ } else {
+ final CharSequence errorText =
+ mFingerprintDialogFragment.getNegativeButtonText();
+ mAuthenticationCallback.onAuthenticationError(
+ ERROR_NEGATIVE_BUTTON, errorText != null ? errorText : "");
+ mFingerprintHelperFragment.cancel(
+ FingerprintHelperFragment.USER_CANCELED_FROM_NEGATIVE_BUTTON);
}
});
}
@@ -448,21 +490,14 @@
@OnLifecycleEvent(Lifecycle.Event.ON_PAUSE)
void onPause() {
if (!isChangingConfigurations()) {
- if (Build.VERSION.SDK_INT < Build.VERSION_CODES.P && !DEBUG_FORCE_FINGERPRINT) {
+ if (usingBiometricFragment()) {
// May be null if no authentication is occurring.
- if (mFingerprintDialogFragment != null) {
- mFingerprintDialogFragment.dismiss();
- }
- if (mFingerprintHelperFragment != null) {
- mFingerprintHelperFragment.cancel(
- FingerprintHelperFragment.USER_CANCELED_FROM_NONE);
- }
- } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
- // TODO(b/123378871): Change == to >= if this bug is not resolved in R.
- // Ignore the first onPause if setDeviceCredentialAllowed is true, since
- // the Q implementation launches ConfirmDeviceCredentialActivity which is an
- // activity and puts the client app onPause.
if (mBiometricFragment != null) {
+ // TODO(b/123378871): Fix behavior in R and remove this workaround.
+ // Ignore the first onPause if isDeviceCredentialAllowed is true, since
+ // the Q implementation launches ConfirmDeviceCredentialActivity, which puts
+ // the client app onPause. Implementations prior to Q instead launch
+ // DeviceCredentialHandlerActivity, resulting in the same problem.
if (mBiometricFragment.isDeviceCredentialAllowed()) {
if (!mPausedOnce) {
mPausedOnce = true;
@@ -473,17 +508,20 @@
mBiometricFragment.cancel();
}
}
- } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
- if (mBiometricFragment != null) {
- mBiometricFragment.cancel();
+ } else {
+ // May be null if no authentication is occurring.
+ if (mFingerprintDialogFragment != null && mFingerprintHelperFragment != null) {
+ dismissFingerprintFragments(mFingerprintDialogFragment,
+ mFingerprintHelperFragment);
}
}
+ maybeResetHandlerBridge();
}
}
@OnLifecycleEvent(Lifecycle.Event.ON_RESUME)
void onResume() {
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P && !DEBUG_FORCE_FINGERPRINT) {
+ if (usingBiometricFragment()) {
mBiometricFragment =
(BiometricFragment) getFragmentManager().findFragmentByTag(
BIOMETRIC_FRAGMENT_TAG);
@@ -508,12 +546,15 @@
mFingerprintHelperFragment.setHandler(mFingerprintDialogFragment.getHandler());
}
}
+
+ maybeHandleDeviceCredentialResult();
+ maybeInitHandlerBridge(false /* ignoreNextReset */);
}
};
/**
* Constructs a {@link BiometricPrompt} which can be used to prompt the user for
- * authentication. The authenticaton prompt created by
+ * authentication. The authentication prompt created by
* {@link BiometricPrompt#authenticate(PromptInfo, CryptoObject)} and
* {@link BiometricPrompt#authenticate(PromptInfo)} will persist across device
* configuration changes by default. If authentication is in progress, re-creating
@@ -523,8 +564,8 @@
* such as {@link FragmentActivity#onCreate(Bundle)}.
*
* @param fragmentActivity A reference to the client's activity.
- * @param executor An executor to handle callback events.
- * @param callback An object to receive authentication events.
+ * @param executor An executor to handle callback events.
+ * @param callback An object to receive authentication events.
*/
@SuppressLint("LambdaLast")
public BiometricPrompt(@NonNull FragmentActivity fragmentActivity,
@@ -582,8 +623,9 @@
/**
* Shows the biometric prompt. The prompt survives lifecycle changes by default. To cancel the
* authentication, use {@link #cancelAuthentication()}.
- * @param info The information that will be displayed on the prompt. Create this object using
- * {@link BiometricPrompt.PromptInfo.Builder}.
+ *
+ * @param info The information that will be displayed on the prompt. Create this object using
+ * {@link BiometricPrompt.PromptInfo.Builder}.
* @param crypto The crypto object associated with the authentication.
*/
public void authenticate(@NonNull PromptInfo info, @NonNull CryptoObject crypto) {
@@ -600,6 +642,7 @@
/**
* Shows the biometric prompt. The prompt survives lifecycle changes by default. To cancel the
* authentication, use {@link #cancelAuthentication()}.
+ *
* @param info The information that will be displayed on the prompt. Create this object using
* {@link BiometricPrompt.PromptInfo.Builder}.
*/
@@ -611,12 +654,19 @@
}
private void authenticateInternal(@NonNull PromptInfo info, @Nullable CryptoObject crypto) {
+ mIsHandlingDeviceCredential = info.isHandlingDeviceCredentialResult();
+ if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.P && info.isDeviceCredentialAllowed()
+ && !mIsHandlingDeviceCredential) {
+ launchDeviceCredentialHandler(info);
+ return;
+ }
+
final Bundle bundle = info.getBundle();
final FragmentManager fragmentManager = getFragmentManager();
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P && !DEBUG_FORCE_FINGERPRINT) {
- mPausedOnce = false;
+ mPausedOnce = false;
+ if (usingBiometricFragment()) {
BiometricFragment biometricFragment =
(BiometricFragment) fragmentManager.findFragmentByTag(
BIOMETRIC_FRAGMENT_TAG);
@@ -627,6 +677,7 @@
}
mBiometricFragment.setCallbacks(mExecutor, mNegativeButtonListener,
mAuthenticationCallback);
+
// Set the crypto object.
mBiometricFragment.setCryptoObject(crypto);
mBiometricFragment.setBundle(bundle);
@@ -687,6 +738,7 @@
fragmentManager.beginTransaction().attach(mFingerprintHelperFragment).commit();
}
}
+
// For the case when onResume() is being called right after authenticate,
// we need to make sure that all fragment transactions have been committed.
fragmentManager.executePendingTransactions();
@@ -695,26 +747,162 @@
/**
* Cancels the biometric authentication, and dismisses the dialog upon confirmation from the
* biometric service.
+ *
+ * On P or below, calling this method when the device credential prompt is shown will NOT work
+ * as expected. See {@link PromptInfo.Builder#setDeviceCredentialAllowed(boolean)} for more
+ * details.
*/
public void cancelAuthentication() {
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P && !DEBUG_FORCE_FINGERPRINT) {
+ if (usingBiometricFragment()) {
if (mBiometricFragment != null) {
mBiometricFragment.cancel();
}
+
+ // If we launched a device credential handler activity, also clean up its fragment.
+ if (!mIsHandlingDeviceCredential) {
+ final DeviceCredentialHandlerBridge bridge =
+ DeviceCredentialHandlerBridge.getInstanceIfNotNull();
+ if (bridge != null && bridge.getBiometricFragment() != null) {
+ bridge.getBiometricFragment().cancel();
+ }
+ }
} else {
if (mFingerprintHelperFragment != null && mFingerprintDialogFragment != null) {
- mFingerprintHelperFragment.cancel(
- FingerprintHelperFragment.USER_CANCELED_FROM_NONE);
- mFingerprintDialogFragment.dismiss();
+ dismissFingerprintFragments(mFingerprintDialogFragment, mFingerprintHelperFragment);
}
+
+ // If we launched a device credential handler activity, also clean up its fragment.
+ if (!mIsHandlingDeviceCredential) {
+ final DeviceCredentialHandlerBridge bridge =
+ DeviceCredentialHandlerBridge.getInstanceIfNotNull();
+ if (bridge != null && bridge.getFingerprintDialogFragment() != null
+ && bridge.getFingerprintHelperFragment() != null) {
+ dismissFingerprintFragments(bridge.getFingerprintDialogFragment(),
+ bridge.getFingerprintHelperFragment());
+ }
+ }
+ }
+ }
+
+ /**
+ * Launches a copy of this prompt in a transparent {@link DeviceCredentialHandlerActivity}.
+ * This allows that activity to intercept and handle activity results from {@link
+ * android.app.KeyguardManager#createConfirmDeviceCredentialIntent(CharSequence, CharSequence)}.
+ */
+ private void launchDeviceCredentialHandler(PromptInfo info) {
+ final FragmentActivity activity = getActivity();
+ if (activity == null || activity.isFinishing()) {
+ Log.w(TAG, "Failed to start handler activity. Parent activity was null or finishing.");
+ return;
+ }
+
+ maybeInitHandlerBridge(true /* ignoreNextReset */);
+
+ // Set the handling device credential flag so the new prompt knows not to launch another
+ // instance of the handler activity.
+ final Bundle infoBundle = info.getBundle();
+ infoBundle.putBoolean(KEY_HANDLING_DEVICE_CREDENTIAL_RESULT, true);
+
+ final Intent intent = new Intent(activity, DeviceCredentialHandlerActivity.class);
+ intent.putExtra(DeviceCredentialHandlerActivity.EXTRA_PROMPT_INFO_BUNDLE, infoBundle);
+ activity.startActivity(intent);
+ }
+
+ /**
+ * Creates (if necessary) the singleton bridge used for communication between the client-hosted
+ * prompt and one hosted by {@link DeviceCredentialHandlerActivity}, and initializes all of the
+ * relevant data for the bridge.
+ *
+ * @param ignoreNextReset Whether the bridge should ignore the next call to
+ * {@link DeviceCredentialHandlerBridge#reset()} once initialized.
+ */
+ private void maybeInitHandlerBridge(boolean ignoreNextReset) {
+ // Don't create bridge if DeviceCredentialHandlerActivity isn't needed.
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
+ return;
+ }
+
+ final DeviceCredentialHandlerBridge bridge = DeviceCredentialHandlerBridge.getInstance();
+ if (mIsHandlingDeviceCredential) {
+ if (usingBiometricFragment() && mBiometricFragment != null) {
+ bridge.setBiometricFragment(mBiometricFragment);
+ } else if (mFingerprintDialogFragment != null && mFingerprintHelperFragment != null) {
+ bridge.setFingerprintFragments(mFingerprintDialogFragment,
+ mFingerprintHelperFragment);
+ }
+ } else {
+ // If hosted by the client, register the current activity theme to the bridge.
+ final FragmentActivity activity = getActivity();
+ if (activity != null) {
+ try {
+ bridge.setClientThemeResId(activity.getPackageManager().getActivityInfo(
+ activity.getComponentName(), 0).getThemeResource());
+ } catch (PackageManager.NameNotFoundException e) {
+ Log.e(TAG, "Failed to register client theme to bridge", e);
+ }
+ }
+ }
+ bridge.setCallbacks(mExecutor, mNegativeButtonListener, mAuthenticationCallback);
+
+ if (ignoreNextReset) {
+ bridge.ignoreNextReset();
+ }
+ }
+
+ /**
+ * Checks the handler bridge to see if we've received a result from the confirm device
+ * credential Settings activity. If so, handles that result by calling the appropriate
+ * authentication callback.
+ */
+ private void maybeHandleDeviceCredentialResult() {
+ // Only handle result from the original (not handler-hosted) prompt.
+ if (mIsHandlingDeviceCredential) {
+ return;
+ }
+
+ final DeviceCredentialHandlerBridge bridge =
+ DeviceCredentialHandlerBridge.getInstanceIfNotNull();
+ if (bridge != null) {
+ switch (bridge.getDeviceCredentialResult()) {
+ case DeviceCredentialHandlerBridge.RESULT_SUCCESS:
+ // Device credential auth succeeded. This is incompatible with crypto.
+ mAuthenticationCallback.onAuthenticationSucceeded(
+ new BiometricPrompt.AuthenticationResult(null /* crypto */));
+ bridge.stopIgnoringReset();
+ bridge.reset();
+ break;
+
+ case DeviceCredentialHandlerBridge.RESULT_ERROR:
+ // Device credential auth failed. Assume this is due to the user canceling.
+ final CharSequence errorMsg = getActivity() != null
+ ? getActivity().getString(R.string.generic_error_user_canceled) : "";
+ mAuthenticationCallback.onAuthenticationError(
+ BiometricConstants.ERROR_USER_CANCELED, errorMsg);
+ bridge.stopIgnoringReset();
+ bridge.reset();
+ break;
+ }
+ }
+ }
+
+ /** Cleans up the device credential handler bridge (if it exists) to avoid leaking memory. */
+ private void maybeResetHandlerBridge() {
+ final DeviceCredentialHandlerBridge bridge =
+ DeviceCredentialHandlerBridge.getInstanceIfNotNull();
+ if (bridge != null) {
+ bridge.reset();
}
}
/** Checks if the client is currently changing configurations (e.g., screen orientation). */
private boolean isChangingConfigurations() {
- return (mFragmentActivity != null && mFragmentActivity.isChangingConfigurations())
- || (mFragment != null && mFragment.getActivity() != null
- && mFragment.getActivity().isChangingConfigurations());
+ return getActivity() != null && getActivity().isChangingConfigurations();
+ }
+
+ /** Gets the client activity that is hosting the biometric prompt. */
+ @Nullable
+ private FragmentActivity getActivity() {
+ return mFragmentActivity != null ? mFragmentActivity : mFragment.getActivity();
}
/**
@@ -722,8 +910,26 @@
* manager for a client activity or the child fragment manager for a client fragment.
*/
private FragmentManager getFragmentManager() {
- return mFragmentActivity != null
- ? mFragmentActivity.getSupportFragmentManager()
+ return mFragmentActivity != null ? mFragmentActivity.getSupportFragmentManager()
: mFragment.getChildFragmentManager();
}
+
+ /**
+ * @return True if the prompt handles authentication via {@link BiometricFragment}, or false
+ * if it does so via {@link FingerprintDialogFragment}.
+ */
+ private static boolean usingBiometricFragment() {
+ return Build.VERSION.SDK_INT >= Build.VERSION_CODES.P && !DEBUG_FORCE_FINGERPRINT;
+ }
+
+ /**
+ * Dismisses the given {@link FingerprintDialogFragment} and {@link FingerprintHelperFragment},
+ * both of which must be non-null.
+ */
+ private static void dismissFingerprintFragments(
+ @NonNull FingerprintDialogFragment fingerprintDialogFragment,
+ @NonNull FingerprintHelperFragment fingerprintHelperFragment) {
+ fingerprintDialogFragment.dismiss();
+ fingerprintHelperFragment.cancel(FingerprintHelperFragment.USER_CANCELED_FROM_NONE);
+ }
}
diff --git a/biometric/src/main/java/androidx/biometric/DeviceCredentialHandlerActivity.java b/biometric/src/main/java/androidx/biometric/DeviceCredentialHandlerActivity.java
new file mode 100644
index 0000000..4d8f784
--- /dev/null
+++ b/biometric/src/main/java/androidx/biometric/DeviceCredentialHandlerActivity.java
@@ -0,0 +1,100 @@
+/*
+ * 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.biometric;
+
+import android.annotation.SuppressLint;
+import android.content.Intent;
+import android.os.Bundle;
+import android.util.Log;
+
+import androidx.annotation.Nullable;
+import androidx.annotation.RestrictTo;
+import androidx.appcompat.app.AppCompatActivity;
+
+/**
+ * Transparent activity that is responsible for re-launching the {@link BiometricPrompt} and
+ * handling results from {@link android.app.KeyguardManager#createConfirmDeviceCredentialIntent(
+ * CharSequence, CharSequence)} in order to allow device credential authentication prior to Q.
+ *
+ * @hide
+ */
+@RestrictTo(RestrictTo.Scope.LIBRARY)
+@SuppressLint("SyntheticAccessor")
+public class DeviceCredentialHandlerActivity extends AppCompatActivity {
+ private static final String TAG = "DeviceCredentialHandler";
+
+ static final String EXTRA_PROMPT_INFO_BUNDLE = "prompt_info_bundle";
+
+ @Nullable
+ private DeviceCredentialHandlerBridge mBridge;
+
+ @Override
+ protected void onCreate(@Nullable Bundle savedInstanceState) {
+ // Apply the client activity's theme to ensure proper dialog styling.
+ DeviceCredentialHandlerBridge bridge =
+ DeviceCredentialHandlerBridge.getInstanceIfNotNull();
+ if (bridge != null && bridge.getClientThemeResId() != 0) {
+ setTheme(bridge.getClientThemeResId());
+ getTheme().applyStyle(R.style.TransparentStyle, true /* force */);
+ }
+
+ // Must be called after setting the theme.
+ super.onCreate(savedInstanceState);
+ setTitle(null);
+ setContentView(R.layout.device_credential_handler_activity);
+
+ mBridge = DeviceCredentialHandlerBridge.getInstance();
+ if (mBridge.getExecutor() == null || mBridge.getAuthenticationCallback() == null) {
+ Log.e(TAG, "onCreate: Executor and/or callback was null!");
+ } else {
+ // (Re)connect to and launch a biometric prompt within this activity.
+ final BiometricPrompt biometricPrompt = new BiometricPrompt(this,
+ mBridge.getExecutor(), mBridge.getAuthenticationCallback());
+ final Bundle infoBundle = getIntent().getBundleExtra(EXTRA_PROMPT_INFO_BUNDLE);
+ final BiometricPrompt.PromptInfo info = new BiometricPrompt.PromptInfo(infoBundle);
+ biometricPrompt.authenticate(info);
+ }
+ }
+
+ @Override
+ protected void onPause() {
+ super.onPause();
+
+ // Prevent the client from resetting the bridge in onPause if just changing configuration.
+ if (isChangingConfigurations() && mBridge != null) {
+ mBridge.ignoreNextReset();
+ }
+ }
+
+ // Handles the result of startActivity invoked by the attached BiometricPrompt.
+ @Override
+ protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {
+ super.onActivityResult(requestCode, resultCode, data);
+
+ // Handle result from ConfirmDeviceCredentialActivity.
+ if (mBridge == null || mBridge.getAuthenticationCallback() == null) {
+ Log.e(TAG, "onActivityResult: Bridge or callback was null!");
+ } else if (resultCode == RESULT_OK) {
+ mBridge.setDeviceCredentialResult(DeviceCredentialHandlerBridge.RESULT_SUCCESS);
+ } else {
+ // Treat any non-OK result as a user cancellation.
+ mBridge.setDeviceCredentialResult(DeviceCredentialHandlerBridge.RESULT_ERROR);
+ }
+
+ finish();
+ }
+}
diff --git a/biometric/src/main/java/androidx/biometric/DeviceCredentialHandlerBridge.java b/biometric/src/main/java/androidx/biometric/DeviceCredentialHandlerBridge.java
new file mode 100644
index 0000000..4778afc
--- /dev/null
+++ b/biometric/src/main/java/androidx/biometric/DeviceCredentialHandlerBridge.java
@@ -0,0 +1,285 @@
+/*
+ * 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.biometric;
+
+import android.annotation.SuppressLint;
+import android.content.DialogInterface;
+import android.os.Build;
+
+import androidx.annotation.IntDef;
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.annotation.RestrictTo;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.concurrent.Executor;
+
+/**
+ * Singleton class to facilitate communication between the {@link BiometricPrompt} for the client
+ * activity and the one attached to {@link DeviceCredentialHandlerActivity} when allowing device
+ * credential authentication prior to Q.
+ *
+ * @hide
+ */
+@RestrictTo(RestrictTo.Scope.LIBRARY)
+class DeviceCredentialHandlerBridge {
+ @Nullable
+ private static DeviceCredentialHandlerBridge sInstance;
+
+ private int mClientThemeResId;
+
+ @Nullable
+ private BiometricFragment mBiometricFragment;
+
+ @Nullable
+ private FingerprintDialogFragment mFingerprintDialogFragment;
+
+ @Nullable
+ private FingerprintHelperFragment mFingerprintHelperFragment;
+
+ @Nullable
+ private Executor mExecutor;
+
+ @Nullable
+ private DialogInterface.OnClickListener mOnClickListener;
+
+ @Nullable
+ private BiometricPrompt.AuthenticationCallback mAuthenticationCallback;
+
+ // Possible results from launching the confirm device credential Settings activity.
+ static final int RESULT_NONE = 0;
+ static final int RESULT_SUCCESS = 1;
+ static final int RESULT_ERROR = 2;
+
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef({RESULT_NONE, RESULT_SUCCESS, RESULT_ERROR})
+ private @interface DeviceCredentialResult {}
+
+ private @DeviceCredentialResult int mDeviceCredentialResult = RESULT_NONE;
+
+ // States indicating whether and for how long to ignore calls to reset().
+ private static final int NOT_IGNORING_RESET = 0;
+ private static final int IGNORING_NEXT_RESET = 1;
+ private static final int IGNORING_RESET = 2;
+
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef({NOT_IGNORING_RESET, IGNORING_NEXT_RESET, IGNORING_RESET})
+ private @interface IgnoreResetState {}
+
+ private @IgnoreResetState int mIgnoreResetState = NOT_IGNORING_RESET;
+
+ // Private constructor to enforce singleton pattern.
+ private DeviceCredentialHandlerBridge() {
+ }
+
+ /** @return The singleton bridge, creating it if necessary. */
+ @NonNull
+ static DeviceCredentialHandlerBridge getInstance() {
+ if (sInstance == null) {
+ sInstance = new DeviceCredentialHandlerBridge();
+ }
+ return sInstance;
+ }
+
+ /** @return The singleton bridge if already created, or null otherwise. */
+ @Nullable
+ static DeviceCredentialHandlerBridge getInstanceIfNotNull() {
+ return sInstance;
+ }
+
+ /**
+ * Register the resource ID for the client activity's theme to the bridge. This will be used
+ * for styling dialogs and other views in the handler activity.
+ */
+ void setClientThemeResId(int clientThemeResId) {
+ mClientThemeResId = clientThemeResId;
+ }
+
+ /** @return See {@link #setClientThemeResId(int)}. */
+ int getClientThemeResId() {
+ return mClientThemeResId;
+ }
+
+ /**
+ * Registers a {@link BiometricFragment} to the bridge. This will automatically receive new
+ * callbacks set by {@link #setCallbacks(Executor, DialogInterface.OnClickListener,
+ * BiometricPrompt.AuthenticationCallback)}.
+ */
+ void setBiometricFragment(@Nullable BiometricFragment biometricFragment) {
+ mBiometricFragment = biometricFragment;
+ }
+
+ /** @return See {@link #setBiometricFragment(BiometricFragment)}. */
+ @Nullable
+ BiometricFragment getBiometricFragment() {
+ return mBiometricFragment;
+ }
+
+ /**
+ * Registers a {@link FingerprintDialogFragment} and {@link FingerprintHelperFragment} to the
+ * bridge. These will automatically receive new callbacks set by {@link #setCallbacks(Executor,
+ * DialogInterface.OnClickListener, BiometricPrompt.AuthenticationCallback)}.
+ */
+ void setFingerprintFragments(@Nullable FingerprintDialogFragment fingerprintDialogFragment,
+ @Nullable FingerprintHelperFragment fingerprintHelperFragment) {
+ mFingerprintDialogFragment = fingerprintDialogFragment;
+ mFingerprintHelperFragment = fingerprintHelperFragment;
+ }
+
+ /**
+ * @return The latest {@link FingerprintDialogFragment} set via
+ * {@link #setFingerprintFragments(FingerprintDialogFragment, FingerprintHelperFragment)}.
+ */
+ @Nullable
+ public FingerprintDialogFragment getFingerprintDialogFragment() {
+ return mFingerprintDialogFragment;
+ }
+
+ /**
+ * @return The latest {@link FingerprintHelperFragment} set via
+ * {@link #setFingerprintFragments(FingerprintDialogFragment, FingerprintHelperFragment)}.
+ */
+ @Nullable
+ public FingerprintHelperFragment getFingerprintHelperFragment() {
+ return mFingerprintHelperFragment;
+ }
+
+ /**
+ * Registers dialog and authentication callbacks to the bridge, along with an executor that can
+ * be used to run them.
+ *
+ * If a {@link BiometricFragment} has been registered via
+ * {@link #setBiometricFragment(BiometricFragment)}, or if a {@link FingerprintDialogFragment}
+ * and {@link FingerprintHelperFragment} have been registered via
+ * {@link #setFingerprintFragments(FingerprintDialogFragment, FingerprintHelperFragment)}, then
+ * these fragments will receive the updated executor and callbacks as well.
+ *
+ * @param executor An executor that can be used to run callbacks.
+ * @param onClickListener A dialog button listener for a biometric prompt.
+ * @param authenticationCallback A handler for various biometric prompt authentication events.
+ */
+ @SuppressLint("LambdaLast")
+ void setCallbacks(@NonNull Executor executor,
+ @NonNull DialogInterface.OnClickListener onClickListener,
+ @NonNull BiometricPrompt.AuthenticationCallback authenticationCallback) {
+ mExecutor = executor;
+ mOnClickListener = onClickListener;
+ mAuthenticationCallback = authenticationCallback;
+ if (mBiometricFragment != null && Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
+ mBiometricFragment.setCallbacks(executor, onClickListener, authenticationCallback);
+ } else if (mFingerprintDialogFragment != null && mFingerprintHelperFragment != null) {
+ mFingerprintDialogFragment.setNegativeButtonListener(onClickListener);
+ mFingerprintHelperFragment.setCallback(executor, authenticationCallback);
+ mFingerprintHelperFragment.setHandler(mFingerprintDialogFragment.getHandler());
+ }
+ }
+
+ /**
+ * @return The latest {@link Executor} set via {@link #setCallbacks(Executor,
+ * DialogInterface.OnClickListener, BiometricPrompt.AuthenticationCallback)}.
+ */
+ @Nullable
+ Executor getExecutor() {
+ return mExecutor;
+ }
+
+ /**
+ * @return The latest {@link DialogInterface.OnClickListener} set via {@link #setCallbacks(
+ * Executor, DialogInterface.OnClickListener, BiometricPrompt.AuthenticationCallback)}.
+ */
+ @Nullable
+ DialogInterface.OnClickListener getOnClickListener() {
+ return mOnClickListener;
+ }
+
+ /**
+ * @return The latest {@link BiometricPrompt.AuthenticationCallback} set via
+ * {@link #setCallbacks(Executor, DialogInterface.OnClickListener,
+ * BiometricPrompt.AuthenticationCallback)}.
+ */
+ @Nullable
+ BiometricPrompt.AuthenticationCallback getAuthenticationCallback() {
+ return mAuthenticationCallback;
+ }
+
+ /**
+ * Stores the authentication result from launching the confirm device credential Settings
+ * activity. This is intended for the client's {@link BiometricPrompt} instance to read this
+ * result and invoke the appropriate authentication callback method.
+ */
+ void setDeviceCredentialResult(int deviceCredentialResult) {
+ mDeviceCredentialResult = deviceCredentialResult;
+ }
+
+ /** @return See {@link #setDeviceCredentialResult(int)}. */
+ int getDeviceCredentialResult() {
+ return mDeviceCredentialResult;
+ }
+
+ /**
+ * Indicates that the bridge should ignore the next call to {@link #reset}. Calling this method
+ * after {@link #startIgnoringReset()} but before {@link #stopIgnoringReset()} has no effect.
+ */
+ void ignoreNextReset() {
+ if (mIgnoreResetState == NOT_IGNORING_RESET) {
+ mIgnoreResetState = IGNORING_NEXT_RESET;
+ }
+ }
+
+ /**
+ * Indicates that the bridge should ignore all subsequent calls to {@link #reset} until
+ * {@link #stopIgnoringReset()} is called.
+ */
+ void startIgnoringReset() {
+ mIgnoreResetState = IGNORING_RESET;
+ }
+
+ /**
+ * When called after {@link #ignoreNextReset()} or {@link #startIgnoringReset()}, allows
+ * subsequent calls to {@link #reset} to go through as normal, until either is called again.
+ */
+ void stopIgnoringReset() {
+ mIgnoreResetState = NOT_IGNORING_RESET;
+ }
+
+ /**
+ * Clears all data associated with the bridge, returning it to its default state.
+ *
+ * Note that calls to this method may be ignored if {@link #ignoreNextReset()} or
+ * {@link #startIgnoringReset()} has been called without a corresponding call to
+ * {@link #stopIgnoringReset()}.
+ */
+ void reset() {
+ if (mIgnoreResetState == IGNORING_RESET) {
+ return;
+ }
+
+ if (mIgnoreResetState == IGNORING_NEXT_RESET) {
+ stopIgnoringReset();
+ return;
+ }
+
+ mBiometricFragment = null;
+ mFingerprintDialogFragment = null;
+ mFingerprintHelperFragment = null;
+ mExecutor = null;
+ mOnClickListener = null;
+ mAuthenticationCallback = null;
+ sInstance = null;
+ }
+}
diff --git a/biometric/src/main/java/androidx/biometric/FingerprintDialogFragment.java b/biometric/src/main/java/androidx/biometric/FingerprintDialogFragment.java
index a46d54c..286958a 100644
--- a/biometric/src/main/java/androidx/biometric/FingerprintDialogFragment.java
+++ b/biometric/src/main/java/androidx/biometric/FingerprintDialogFragment.java
@@ -16,9 +16,12 @@
package androidx.biometric;
+import android.annotation.SuppressLint;
import android.app.Dialog;
+import android.app.KeyguardManager;
import android.content.Context;
import android.content.DialogInterface;
+import android.content.Intent;
import android.content.res.Resources;
import android.content.res.TypedArray;
import android.graphics.drawable.AnimatedVectorDrawable;
@@ -27,29 +30,35 @@
import android.os.Bundle;
import android.os.Handler;
import android.text.TextUtils;
+import android.util.Log;
import android.util.TypedValue;
import android.view.LayoutInflater;
import android.view.View;
import android.widget.ImageView;
import android.widget.TextView;
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
import androidx.annotation.RequiresApi;
import androidx.annotation.RestrictTo;
import androidx.appcompat.app.AlertDialog;
import androidx.core.content.ContextCompat;
import androidx.fragment.app.DialogFragment;
+import androidx.fragment.app.FragmentActivity;
/**
* This class implements a custom AlertDialog that prompts the user for fingerprint authentication.
* This class is not meant to be preserved across process death; for security reasons, the
* BiometricPromptCompat will automatically dismiss the dialog when the activity is no longer in the
* foreground.
+ *
* @hide
*/
@RestrictTo(RestrictTo.Scope.LIBRARY)
+@SuppressLint("SyntheticAccessor")
public class FingerprintDialogFragment extends DialogFragment {
- private static final String TAG = "FingerprintDialogFragment";
+ private static final String TAG = "FingerprintDialogFrag";
private static final String KEY_DIALOG_BUNDLE = "SavedBundle";
/**
@@ -58,21 +67,21 @@
* Error messages will be propagated back to the application via AuthenticationCallback
* after this amount of time.
*/
- protected static final int HIDE_DIALOG_DELAY = 2000; // ms
+ static final int HIDE_DIALOG_DELAY = 2000; // ms
// Shows a temporary message in the help area
- protected static final int MSG_SHOW_HELP = 1;
+ static final int MSG_SHOW_HELP = 1;
// Show an error in the help area, and dismiss the dialog afterwards
- protected static final int MSG_SHOW_ERROR = 2;
+ static final int MSG_SHOW_ERROR = 2;
// Dismisses the authentication dialog
- protected static final int MSG_DISMISS_DIALOG_ERROR = 3;
+ static final int MSG_DISMISS_DIALOG_ERROR = 3;
// Resets the help message
- protected static final int MSG_RESET_MESSAGE = 4;
+ static final int MSG_RESET_MESSAGE = 4;
// Dismisses the authentication dialog after success.
- protected static final int MSG_DISMISS_DIALOG_AUTHENTICATED = 5;
+ static final int MSG_DISMISS_DIALOG_AUTHENTICATED = 5;
// The amount of time required that this fragment be displayed for in order that
// we show an error message on top of the UI.
- protected static final int DISPLAYED_FOR_500_MS = 6;
+ static final int DISPLAYED_FOR_500_MS = 6;
// States for icon animation
private static final int STATE_NONE = 0;
@@ -83,7 +92,7 @@
/**
* Creates a dialog requesting for Fingerprint authentication.
*/
- public static FingerprintDialogFragment newInstance() {
+ static FingerprintDialogFragment newInstance() {
FingerprintDialogFragment fragment = new FingerprintDialogFragment();
return fragment;
}
@@ -123,20 +132,79 @@
private TextView mErrorText;
private Context mContext;
- private Dialog mDialog;
+
/**
* This flag is used to control the instant dismissal of the dialog fragment. In the case where
* the user is already locked out this dialog will not appear. In the case where the user is
* being locked out for the first time an error message will be displayed on the UI before
* dismissing.
*/
- protected boolean mDismissInstantly = true;
+ private boolean mDismissInstantly = true;
// This should be re-set by the BiometricPromptCompat each time the lifecycle changes.
- DialogInterface.OnClickListener mNegativeButtonListener;
+ private DialogInterface.OnClickListener mNegativeButtonListener;
+
+ // Also created once and retained.
+ @SuppressWarnings("deprecation")
+ private final DialogInterface.OnClickListener mDeviceCredentialButtonListener =
+ (dialog, which) -> {
+ if (which == DialogInterface.BUTTON_NEGATIVE) {
+ if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) {
+ Log.e(TAG, "Failed to check device credential. Not supported prior to L.");
+ return;
+ }
+
+ final FragmentActivity activity = getActivity();
+ if (!(activity instanceof DeviceCredentialHandlerActivity)) {
+ Log.e(TAG, "Failed to check device credential. Parent handler not found.");
+ return;
+ }
+
+ final Object service = activity.getSystemService(Context.KEYGUARD_SERVICE);
+ if (!(service instanceof KeyguardManager)) {
+ Log.e(TAG, "Failed to check device credential. KeyguardManager not found.");
+ return;
+ }
+ final KeyguardManager km = (KeyguardManager) service;
+
+ // Dismiss the fingerprint dialog without forwarding errors to the client.
+ final FingerprintHelperFragment fingerprintHelperFragment =
+ (FingerprintHelperFragment) getFragmentManager().findFragmentByTag(
+ BiometricPrompt.FINGERPRINT_HELPER_FRAGMENT_TAG);
+ if (fingerprintHelperFragment != null) {
+ fingerprintHelperFragment.setConfirmingDeviceCredential(true);
+ }
+ onCancel(dialog);
+
+ // Pass along the title and subtitle from the biometric prompt.
+ final CharSequence title;
+ final CharSequence subtitle;
+ if (mBundle != null) {
+ title = mBundle.getCharSequence(BiometricPrompt.KEY_TITLE);
+ subtitle = mBundle.getCharSequence(BiometricPrompt.KEY_SUBTITLE);
+ } else {
+ title = null;
+ subtitle = null;
+ }
+
+ // Prevent the bridge from resetting until the confirmation activity finishes.
+ DeviceCredentialHandlerBridge bridge =
+ DeviceCredentialHandlerBridge.getInstanceIfNotNull();
+ if (bridge != null) {
+ bridge.startIgnoringReset();
+ }
+
+ // Launch a new instance of the confirm device credential Settings activity.
+ final Intent intent = km.createConfirmDeviceCredentialIntent(title, subtitle);
+ intent.setFlags(
+ Intent.FLAG_ACTIVITY_MULTIPLE_TASK | Intent.FLAG_ACTIVITY_NEW_DOCUMENT);
+ activity.startActivityForResult(intent, 0 /* requestCode */);
+ }
+ };
@Override
- public Dialog onCreateDialog(Bundle savedInstanceState) {
+ @NonNull
+ public Dialog onCreateDialog(@Nullable Bundle savedInstanceState) {
if (savedInstanceState != null && mBundle == null) {
mBundle = savedInstanceState.getBundle(KEY_DIALOG_BUNDLE);
}
@@ -174,30 +242,33 @@
mErrorText = layout.findViewById(R.id.fingerprint_error);
final CharSequence negativeButtonText =
- mBundle.getCharSequence(BiometricPrompt.KEY_NEGATIVE_TEXT);
- builder.setNegativeButton(negativeButtonText, new DialogInterface.OnClickListener() {
- @Override
- public void onClick(DialogInterface dialog, int which) {
- if (mNegativeButtonListener != null) {
- mNegativeButtonListener.onClick(dialog, which);
- }
+ isDeviceCredentialAllowed()
+ ? getString(R.string.confirm_device_credential_password)
+ : mBundle.getCharSequence(BiometricPrompt.KEY_NEGATIVE_TEXT);
+ builder.setNegativeButton(negativeButtonText, (dialog, which) -> {
+ if (isDeviceCredentialAllowed()) {
+ mDeviceCredentialButtonListener.onClick(dialog, which);
+ } else if (mNegativeButtonListener != null) {
+ mNegativeButtonListener.onClick(dialog, which);
+ } else {
+ Log.w(TAG, "No suitable negative button listener.");
}
});
builder.setView(layout);
- mDialog = builder.create();
- mDialog.setCanceledOnTouchOutside(false);
- return mDialog;
+ Dialog dialog = builder.create();
+ dialog.setCanceledOnTouchOutside(false);
+ return dialog;
}
@Override
- public void onSaveInstanceState(Bundle outState) {
+ public void onSaveInstanceState(@NonNull Bundle outState) {
super.onSaveInstanceState(outState);
outState.putBundle(KEY_DIALOG_BUNDLE, mBundle);
}
@Override
- public void onCreate(Bundle savedInstanceState) {
+ public void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mContext = getContext();
@@ -224,9 +295,9 @@
}
@Override
- public void onCancel(DialogInterface dialog) {
+ public void onCancel(@NonNull DialogInterface dialog) {
super.onCancel(dialog);
- FingerprintHelperFragment fingerprintHelperFragment = (FingerprintHelperFragment)
+ final FingerprintHelperFragment fingerprintHelperFragment = (FingerprintHelperFragment)
getFragmentManager()
.findFragmentByTag(BiometricPrompt.FINGERPRINT_HELPER_FRAGMENT_TAG);
if (fingerprintHelperFragment != null) {
@@ -234,7 +305,7 @@
}
}
- public void setBundle(Bundle bundle) {
+ public void setBundle(@NonNull Bundle bundle) {
mBundle = bundle;
}
@@ -254,27 +325,27 @@
* the dialog persists through rotation, this allows us to return this as the error text for
* ERROR_NEGATIVE_BUTTON.
*/
+ @Nullable
protected CharSequence getNegativeButtonText() {
return mBundle.getCharSequence(BiometricPrompt.KEY_NEGATIVE_TEXT);
}
- /**
- * Sets the negative button listener.
- * @param listener
- */
- protected void setNegativeButtonListener(DialogInterface.OnClickListener listener) {
+ void setNegativeButtonListener(DialogInterface.OnClickListener listener) {
mNegativeButtonListener = listener;
}
/**
- * Returns the handler; the handler is used by FingerprintHelperFragment to notify the UI of
+ * @return The handler; the handler is used by FingerprintHelperFragment to notify the UI of
* changes from Fingerprint callbacks.
- * @return
*/
- protected Handler getHandler() {
+ Handler getHandler() {
return mHandler;
}
+ boolean isDeviceCredentialAllowed() {
+ return mBundle.getBoolean(BiometricPrompt.KEY_ALLOW_DEVICE_CREDENTIAL);
+ }
+
private boolean shouldAnimateForTransition(int oldState, int newState) {
if (oldState == STATE_NONE && newState == STATE_FINGERPRINT) {
return false;
@@ -289,7 +360,7 @@
return false;
}
- @RequiresApi(21)
+ @RequiresApi(Build.VERSION_CODES.LOLLIPOP)
private Drawable getAnimationForTransition(int oldState, int newState) {
int iconRes;
@@ -332,7 +403,7 @@
}
}
- void handleShowHelp(CharSequence msg) {
+ private void handleShowHelp(CharSequence msg) {
updateFingerprintIcon(STATE_FINGERPRINT_ERROR);
mHandler.removeMessages(MSG_RESET_MESSAGE);
mErrorText.setTextColor(mErrorColor);
@@ -342,7 +413,7 @@
mHandler.sendMessageDelayed(mHandler.obtainMessage(MSG_RESET_MESSAGE), HIDE_DIALOG_DELAY);
}
- void handleShowError(int errMsgId, CharSequence msg) {
+ private void handleShowError(int errMsgId, CharSequence msg) {
updateFingerprintIcon(STATE_FINGERPRINT_ERROR);
mHandler.removeMessages(MSG_RESET_MESSAGE);
mErrorText.setTextColor(mErrorColor);
@@ -353,19 +424,13 @@
HIDE_DIALOG_DELAY);
}
- void dismissAfterDelay() {
+ private void dismissAfterDelay() {
mErrorText.setTextColor(mErrorColor);
- mErrorText.setText(
- R.string.fingerprint_error_lockout);
- mHandler.postDelayed(new Runnable() {
- @Override
- public void run() {
- dismiss();
- }
- }, HIDE_DIALOG_DELAY);
+ mErrorText.setText(R.string.fingerprint_error_lockout);
+ mHandler.postDelayed(this::dismiss, HIDE_DIALOG_DELAY);
}
- void handleDismissDialogError() {
+ private void handleDismissDialogError() {
if (mDismissInstantly) {
dismiss();
} else {
@@ -376,7 +441,7 @@
mDismissInstantly = true;
}
- void handleResetMessage() {
+ private void handleResetMessage() {
updateFingerprintIcon(STATE_FINGERPRINT);
mErrorText.setTextColor(mTextColor);
mErrorText.setText(mContext.getString(R.string.fingerprint_dialog_touch_sensor));
diff --git a/biometric/src/main/java/androidx/biometric/FingerprintHelperFragment.java b/biometric/src/main/java/androidx/biometric/FingerprintHelperFragment.java
index 219e3c4..173c607 100644
--- a/biometric/src/main/java/androidx/biometric/FingerprintHelperFragment.java
+++ b/biometric/src/main/java/androidx/biometric/FingerprintHelperFragment.java
@@ -16,6 +16,7 @@
package androidx.biometric;
+import android.annotation.SuppressLint;
import android.content.Context;
import android.os.Bundle;
import android.os.Handler;
@@ -24,9 +25,12 @@
import android.view.View;
import android.view.ViewGroup;
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
import androidx.annotation.RestrictTo;
import androidx.core.os.CancellationSignal;
import androidx.fragment.app.Fragment;
+import androidx.fragment.app.FragmentActivity;
import java.util.concurrent.Executor;
@@ -35,34 +39,39 @@
* across device configuration changes. This class is not meant to be preserved after process death;
* for security reasons, the BiometricPromptCompat will automatically stop authentication when the
* activity is no longer in the foreground.
+ *
* @hide
*/
@RestrictTo(RestrictTo.Scope.LIBRARY)
+@SuppressLint("SyntheticAccessor")
public class FingerprintHelperFragment extends Fragment {
- private static final String TAG = "fh_fragment";
+ private static final String TAG = "FingerprintHelperFrag";
- protected static final int USER_CANCELED_FROM_NONE = 0;
- protected static final int USER_CANCELED_FROM_USER = 1;
- protected static final int USER_CANCELED_FROM_NEGATIVE_BUTTON = 2;
+ static final int USER_CANCELED_FROM_NONE = 0;
+ static final int USER_CANCELED_FROM_USER = 1;
+ static final int USER_CANCELED_FROM_NEGATIVE_BUTTON = 2;
// Re-set by the application, through BiometricPromptCompat upon orientation changes.
- Executor mExecutor;
- BiometricPrompt.AuthenticationCallback mClientAuthenticationCallback;
+ private Executor mExecutor;
+ private BiometricPrompt.AuthenticationCallback mClientAuthenticationCallback;
// Re-set by BiometricPromptCompat upon orientation changes. This handler is used to send
// messages from the AuthenticationCallbacks to the UI.
- Handler mHandler;
+ private Handler mHandler;
// Set once and retained.
private boolean mShowing;
private BiometricPrompt.CryptoObject mCryptoObject;
// Created once and retained.
- Context mContext;
- int mCanceledFrom;
+ private Context mContext;
+ private int mCanceledFrom;
private CancellationSignal mCancellationSignal;
+ // Whether this fragment is launching the confirm device credential Settings activity.
+ private boolean mConfirmingDeviceCredential;
+
// Also created once and retained.
@SuppressWarnings("deprecation")
private final androidx.core.hardware.fingerprint.FingerprintManagerCompat.AuthenticationCallback
@@ -73,13 +82,11 @@
final CharSequence errString) {
mHandler.obtainMessage(FingerprintDialogFragment.MSG_DISMISS_DIALOG_ERROR)
.sendToTarget();
- mExecutor.execute(new Runnable() {
- @Override
- public void run() {
- mClientAuthenticationCallback
- .onAuthenticationError(errMsgId, errString);
- }
- });
+ if (!mConfirmingDeviceCredential) {
+ mExecutor.execute(
+ () -> mClientAuthenticationCallback.onAuthenticationError(errMsgId,
+ errString));
+ }
}
@Override
@@ -110,18 +117,12 @@
mHandler.obtainMessage(FingerprintDialogFragment.MSG_SHOW_ERROR,
errMsgIdToSend, 0, errStringNonNull).sendToTarget();
- mHandler.postDelayed(new Runnable() {
- @Override
- public void run() {
- mExecutor.execute(new Runnable() {
- @Override
- public void run() {
- mClientAuthenticationCallback.onAuthenticationError(
- errMsgIdToSend, errStringNonNull);
- }
- });
- }
- }, FingerprintDialogFragment.HIDE_DIALOG_DELAY);
+ if (!mConfirmingDeviceCredential) {
+ mHandler.postDelayed(() -> mExecutor.execute(
+ () -> mClientAuthenticationCallback.onAuthenticationError(
+ errMsgIdToSend, errStringNonNull)),
+ FingerprintDialogFragment.HIDE_DIALOG_DELAY);
+ }
}
cleanup();
}
@@ -140,14 +141,9 @@
mHandler.obtainMessage(
FingerprintDialogFragment.MSG_DISMISS_DIALOG_AUTHENTICATED)
.sendToTarget();
- mExecutor.execute(new Runnable() {
- @Override
- public void run() {
- mClientAuthenticationCallback.onAuthenticationSucceeded(
- new BiometricPrompt.AuthenticationResult(
- unwrapCryptoObject(result.getCryptoObject())));
- }
- });
+ mExecutor.execute(() -> mClientAuthenticationCallback.onAuthenticationSucceeded(
+ new BiometricPrompt.AuthenticationResult(
+ unwrapCryptoObject(result.getCryptoObject()))));
cleanup();
}
@@ -156,19 +152,14 @@
mHandler.obtainMessage(FingerprintDialogFragment.MSG_SHOW_HELP,
mContext.getResources().getString(R.string.fingerprint_not_recognized))
.sendToTarget();
- mExecutor.execute(new Runnable() {
- @Override
- public void run() {
- mClientAuthenticationCallback.onAuthenticationFailed();
- }
- });
+ mExecutor.execute(mClientAuthenticationCallback::onAuthenticationFailed);
}
};
/**
* Creates a new instance of the {@link FingerprintHelperFragment}.
*/
- public static FingerprintHelperFragment newInstance() {
+ static FingerprintHelperFragment newInstance() {
return new FingerprintHelperFragment();
}
@@ -176,22 +167,22 @@
* Sets the crypto object to be associated with the authentication. Should be called before
* adding the fragment to guarantee that it's ready in onCreate().
*/
- public void setCryptoObject(BiometricPrompt.CryptoObject crypto) {
+ void setCryptoObject(BiometricPrompt.CryptoObject crypto) {
mCryptoObject = crypto;
}
@Override
- public void onCreate(Bundle savedInstanceState) {
+ public void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setRetainInstance(true);
mContext = getContext();
-
}
@Override
@SuppressWarnings("deprecation")
- public View onCreateView(LayoutInflater inflater, ViewGroup container,
- Bundle savedInstanceState) {
+ @Nullable
+ public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container,
+ @Nullable Bundle savedInstanceState) {
if (!mShowing) {
mCancellationSignal = new CancellationSignal();
mCanceledFrom = USER_CANCELED_FROM_NONE;
@@ -218,7 +209,7 @@
* Sets the client's callback. This should be done whenever the lifecycle changes (orientation
* changes).
*/
- protected void setCallback(Executor executor,
+ void setCallback(Executor executor,
BiometricPrompt.AuthenticationCallback callback) {
mExecutor = executor;
mClientAuthenticationCallback = callback;
@@ -227,15 +218,23 @@
/**
* Pass a reference to the handler used by FingerprintDialogFragment to update the UI.
*/
- protected void setHandler(Handler handler) {
+ void setHandler(Handler handler) {
mHandler = handler;
}
/**
+ * Indicates whether this fragment has or is about to launch the confirm device credential
+ * Settings activity and should therefore stop sending error signals back to the client.
+ */
+ void setConfirmingDeviceCredential(boolean confirmingDeviceCredential) {
+ mConfirmingDeviceCredential = confirmingDeviceCredential;
+ }
+
+ /**
* Cancel the authentication.
* @param canceledFrom one of the USER_CANCELED_FROM* constants
*/
- protected void cancel(int canceledFrom) {
+ void cancel(int canceledFrom) {
mCanceledFrom = canceledFrom;
if (canceledFrom == USER_CANCELED_FROM_USER) {
sendErrorToClient(BiometricPrompt.ERROR_USER_CANCELED);
@@ -250,11 +249,15 @@
/**
* Remove the fragment so that resources can be freed.
*/
- void cleanup() {
+ private void cleanup() {
mShowing = false;
+ FragmentActivity activity = getActivity();
if (getFragmentManager() != null) {
getFragmentManager().beginTransaction().detach(this).commitAllowingStateLoss();
}
+ if (!mConfirmingDeviceCredential) {
+ Utils.maybeFinishHandler(activity);
+ }
}
/**
@@ -277,10 +280,14 @@
/**
* Bypasses the FingerprintManager authentication callback wrapper and sends it directly to the
* client's callback, since the UI is not even showing yet.
- * @param error
+ *
+ * @param error The error code that will be sent to the client.
*/
private void sendErrorToClient(final int error) {
- mClientAuthenticationCallback.onAuthenticationError(error, getErrorString(mContext, error));
+ if (!mConfirmingDeviceCredential) {
+ mClientAuthenticationCallback.onAuthenticationError(error,
+ getErrorString(mContext, error));
+ }
}
/**
@@ -304,7 +311,7 @@
}
@SuppressWarnings("deprecation")
- static BiometricPrompt.CryptoObject unwrapCryptoObject(
+ private static BiometricPrompt.CryptoObject unwrapCryptoObject(
androidx.core.hardware.fingerprint.FingerprintManagerCompat.CryptoObject cryptoObject) {
if (cryptoObject == null) {
return null;
@@ -320,7 +327,7 @@
}
@SuppressWarnings("deprecation")
- static androidx.core.hardware.fingerprint.FingerprintManagerCompat.CryptoObject
+ private static androidx.core.hardware.fingerprint.FingerprintManagerCompat.CryptoObject
wrapCryptoObject(BiometricPrompt.CryptoObject cryptoObject) {
if (cryptoObject == null) {
return null;
diff --git a/biometric/src/main/java/androidx/biometric/Utils.java b/biometric/src/main/java/androidx/biometric/Utils.java
index 5782399..0fa34f91 100644
--- a/biometric/src/main/java/androidx/biometric/Utils.java
+++ b/biometric/src/main/java/androidx/biometric/Utils.java
@@ -16,7 +16,9 @@
package androidx.biometric;
+import androidx.annotation.Nullable;
import androidx.annotation.RestrictTo;
+import androidx.fragment.app.FragmentActivity;
/**
* @hide
@@ -48,4 +50,14 @@
return true;
}
}
+
+ /**
+ * Finishes a given activity if and only if it's a {@link DeviceCredentialHandlerActivity}.
+ * @param activity The activity to finish.
+ */
+ public static void maybeFinishHandler(@Nullable FragmentActivity activity) {
+ if (activity instanceof DeviceCredentialHandlerActivity && !activity.isFinishing()) {
+ activity.finish();
+ }
+ }
}
diff --git a/browser/api/1.2.0-alpha08.txt b/browser/api/1.2.0-alpha08.txt
index c6aeb2e..efb806f 100644
--- a/browser/api/1.2.0-alpha08.txt
+++ b/browser/api/1.2.0-alpha08.txt
@@ -258,9 +258,12 @@
method public androidx.browser.trusted.TrustedWebActivityIntentBuilder setColorScheme(int);
method public androidx.browser.trusted.TrustedWebActivityIntentBuilder setColorSchemeParams(int, androidx.browser.customtabs.CustomTabColorSchemeParams);
method public androidx.browser.trusted.TrustedWebActivityIntentBuilder setNavigationBarColor(@ColorInt int);
+ method public androidx.browser.trusted.TrustedWebActivityIntentBuilder setShareParams(androidx.browser.trusted.sharing.ShareTarget, androidx.browser.trusted.sharing.ShareData);
method public androidx.browser.trusted.TrustedWebActivityIntentBuilder setSplashScreenParams(android.os.Bundle);
method public androidx.browser.trusted.TrustedWebActivityIntentBuilder setToolbarColor(@ColorInt int);
field public static final String EXTRA_ADDITIONAL_TRUSTED_ORIGINS = "android.support.customtabs.extra.ADDITIONAL_TRUSTED_ORIGINS";
+ field public static final String EXTRA_SHARE_DATA = "androidx.browser.trusted.extra.SHARE_DATA";
+ field public static final String EXTRA_SHARE_TARGET = "androidx.browser.trusted.extra.SHARE_TARGET";
field public static final String EXTRA_SPLASH_SCREEN_PARAMS = "androidx.browser.trusted.EXTRA_SPLASH_SCREEN_PARAMS";
}
@@ -303,6 +306,58 @@
}
+package androidx.browser.trusted.sharing {
+
+ public final class ShareData {
+ ctor public ShareData(String?, String?, java.util.List<android.net.Uri!>?);
+ method public static androidx.browser.trusted.sharing.ShareData fromBundle(android.os.Bundle);
+ method public android.os.Bundle toBundle();
+ field public static final String KEY_TEXT = "androidx.browser.trusted.sharing.KEY_TEXT";
+ field public static final String KEY_TITLE = "androidx.browser.trusted.sharing.KEY_TITLE";
+ field public static final String KEY_URIS = "androidx.browser.trusted.sharing.KEY_URIS";
+ field public final String? text;
+ field public final String? title;
+ field public final java.util.List<android.net.Uri!>? uris;
+ }
+
+ public final class ShareTarget {
+ ctor public ShareTarget(String, String?, String?, androidx.browser.trusted.sharing.ShareTarget.Params);
+ method public static androidx.browser.trusted.sharing.ShareTarget? fromBundle(android.os.Bundle);
+ method public android.os.Bundle toBundle();
+ field public static final String ENCODING_TYPE_MULTIPART = "multipart/form-data";
+ field public static final String ENCODING_TYPE_URL_ENCODED = "application/x-www-form-urlencoded";
+ field public static final String KEY_ACTION = "androidx.browser.trusted.sharing.KEY_ACTION";
+ field public static final String KEY_ENCTYPE = "androidx.browser.trusted.sharing.KEY_ENCTYPE";
+ field public static final String KEY_METHOD = "androidx.browser.trusted.sharing.KEY_METHOD";
+ field public static final String KEY_PARAMS = "androidx.browser.trusted.sharing.KEY_PARAMS";
+ field public static final String METHOD_GET = "GET";
+ field public static final String METHOD_POST = "POST";
+ field public final String action;
+ field public final String? encodingType;
+ field public final String? method;
+ field public final androidx.browser.trusted.sharing.ShareTarget.Params params;
+ }
+
+ public static class ShareTarget.FileFormField {
+ ctor public ShareTarget.FileFormField(String, java.util.List<java.lang.String!>);
+ field public static final String KEY_ACCEPTED_TYPES = "androidx.browser.trusted.sharing.KEY_ACCEPTED_TYPES";
+ field public static final String KEY_NAME = "androidx.browser.trusted.sharing.KEY_FILE_NAME";
+ field public final java.util.List<java.lang.String!> acceptedTypes;
+ field public final String name;
+ }
+
+ public static class ShareTarget.Params {
+ ctor public ShareTarget.Params(String?, String?, java.util.List<androidx.browser.trusted.sharing.ShareTarget.FileFormField!>?);
+ field public static final String KEY_FILES = "androidx.browser.trusted.sharing.KEY_FILES";
+ field public static final String KEY_TEXT = "androidx.browser.trusted.sharing.KEY_TEXT";
+ field public static final String KEY_TITLE = "androidx.browser.trusted.sharing.KEY_TITLE";
+ field public final java.util.List<androidx.browser.trusted.sharing.ShareTarget.FileFormField!>? files;
+ field public final String? text;
+ field public final String? title;
+ }
+
+}
+
package androidx.browser.trusted.splashscreens {
public final class SplashScreenParamKey {
diff --git a/browser/api/current.txt b/browser/api/current.txt
index c6aeb2e..efb806f 100644
--- a/browser/api/current.txt
+++ b/browser/api/current.txt
@@ -258,9 +258,12 @@
method public androidx.browser.trusted.TrustedWebActivityIntentBuilder setColorScheme(int);
method public androidx.browser.trusted.TrustedWebActivityIntentBuilder setColorSchemeParams(int, androidx.browser.customtabs.CustomTabColorSchemeParams);
method public androidx.browser.trusted.TrustedWebActivityIntentBuilder setNavigationBarColor(@ColorInt int);
+ method public androidx.browser.trusted.TrustedWebActivityIntentBuilder setShareParams(androidx.browser.trusted.sharing.ShareTarget, androidx.browser.trusted.sharing.ShareData);
method public androidx.browser.trusted.TrustedWebActivityIntentBuilder setSplashScreenParams(android.os.Bundle);
method public androidx.browser.trusted.TrustedWebActivityIntentBuilder setToolbarColor(@ColorInt int);
field public static final String EXTRA_ADDITIONAL_TRUSTED_ORIGINS = "android.support.customtabs.extra.ADDITIONAL_TRUSTED_ORIGINS";
+ field public static final String EXTRA_SHARE_DATA = "androidx.browser.trusted.extra.SHARE_DATA";
+ field public static final String EXTRA_SHARE_TARGET = "androidx.browser.trusted.extra.SHARE_TARGET";
field public static final String EXTRA_SPLASH_SCREEN_PARAMS = "androidx.browser.trusted.EXTRA_SPLASH_SCREEN_PARAMS";
}
@@ -303,6 +306,58 @@
}
+package androidx.browser.trusted.sharing {
+
+ public final class ShareData {
+ ctor public ShareData(String?, String?, java.util.List<android.net.Uri!>?);
+ method public static androidx.browser.trusted.sharing.ShareData fromBundle(android.os.Bundle);
+ method public android.os.Bundle toBundle();
+ field public static final String KEY_TEXT = "androidx.browser.trusted.sharing.KEY_TEXT";
+ field public static final String KEY_TITLE = "androidx.browser.trusted.sharing.KEY_TITLE";
+ field public static final String KEY_URIS = "androidx.browser.trusted.sharing.KEY_URIS";
+ field public final String? text;
+ field public final String? title;
+ field public final java.util.List<android.net.Uri!>? uris;
+ }
+
+ public final class ShareTarget {
+ ctor public ShareTarget(String, String?, String?, androidx.browser.trusted.sharing.ShareTarget.Params);
+ method public static androidx.browser.trusted.sharing.ShareTarget? fromBundle(android.os.Bundle);
+ method public android.os.Bundle toBundle();
+ field public static final String ENCODING_TYPE_MULTIPART = "multipart/form-data";
+ field public static final String ENCODING_TYPE_URL_ENCODED = "application/x-www-form-urlencoded";
+ field public static final String KEY_ACTION = "androidx.browser.trusted.sharing.KEY_ACTION";
+ field public static final String KEY_ENCTYPE = "androidx.browser.trusted.sharing.KEY_ENCTYPE";
+ field public static final String KEY_METHOD = "androidx.browser.trusted.sharing.KEY_METHOD";
+ field public static final String KEY_PARAMS = "androidx.browser.trusted.sharing.KEY_PARAMS";
+ field public static final String METHOD_GET = "GET";
+ field public static final String METHOD_POST = "POST";
+ field public final String action;
+ field public final String? encodingType;
+ field public final String? method;
+ field public final androidx.browser.trusted.sharing.ShareTarget.Params params;
+ }
+
+ public static class ShareTarget.FileFormField {
+ ctor public ShareTarget.FileFormField(String, java.util.List<java.lang.String!>);
+ field public static final String KEY_ACCEPTED_TYPES = "androidx.browser.trusted.sharing.KEY_ACCEPTED_TYPES";
+ field public static final String KEY_NAME = "androidx.browser.trusted.sharing.KEY_FILE_NAME";
+ field public final java.util.List<java.lang.String!> acceptedTypes;
+ field public final String name;
+ }
+
+ public static class ShareTarget.Params {
+ ctor public ShareTarget.Params(String?, String?, java.util.List<androidx.browser.trusted.sharing.ShareTarget.FileFormField!>?);
+ field public static final String KEY_FILES = "androidx.browser.trusted.sharing.KEY_FILES";
+ field public static final String KEY_TEXT = "androidx.browser.trusted.sharing.KEY_TEXT";
+ field public static final String KEY_TITLE = "androidx.browser.trusted.sharing.KEY_TITLE";
+ field public final java.util.List<androidx.browser.trusted.sharing.ShareTarget.FileFormField!>? files;
+ field public final String? text;
+ field public final String? title;
+ }
+
+}
+
package androidx.browser.trusted.splashscreens {
public final class SplashScreenParamKey {
diff --git a/browser/api/restricted_1.2.0-alpha08.txt b/browser/api/restricted_1.2.0-alpha08.txt
index 1e2b761..ae6035ea 100644
--- a/browser/api/restricted_1.2.0-alpha08.txt
+++ b/browser/api/restricted_1.2.0-alpha08.txt
@@ -275,9 +275,12 @@
method public androidx.browser.trusted.TrustedWebActivityIntentBuilder setColorScheme(int);
method public androidx.browser.trusted.TrustedWebActivityIntentBuilder setColorSchemeParams(int, androidx.browser.customtabs.CustomTabColorSchemeParams);
method public androidx.browser.trusted.TrustedWebActivityIntentBuilder setNavigationBarColor(@ColorInt int);
+ method public androidx.browser.trusted.TrustedWebActivityIntentBuilder setShareParams(androidx.browser.trusted.sharing.ShareTarget, androidx.browser.trusted.sharing.ShareData);
method public androidx.browser.trusted.TrustedWebActivityIntentBuilder setSplashScreenParams(android.os.Bundle);
method public androidx.browser.trusted.TrustedWebActivityIntentBuilder setToolbarColor(@ColorInt int);
field public static final String EXTRA_ADDITIONAL_TRUSTED_ORIGINS = "android.support.customtabs.extra.ADDITIONAL_TRUSTED_ORIGINS";
+ field public static final String EXTRA_SHARE_DATA = "androidx.browser.trusted.extra.SHARE_DATA";
+ field public static final String EXTRA_SHARE_TARGET = "androidx.browser.trusted.extra.SHARE_TARGET";
field public static final String EXTRA_SPLASH_SCREEN_PARAMS = "androidx.browser.trusted.EXTRA_SPLASH_SCREEN_PARAMS";
}
@@ -320,6 +323,60 @@
}
+package androidx.browser.trusted.sharing {
+
+ public final class ShareData {
+ ctor public ShareData(String?, String?, java.util.List<android.net.Uri!>?);
+ method public static androidx.browser.trusted.sharing.ShareData fromBundle(android.os.Bundle);
+ method public android.os.Bundle toBundle();
+ field public static final String KEY_TEXT = "androidx.browser.trusted.sharing.KEY_TEXT";
+ field public static final String KEY_TITLE = "androidx.browser.trusted.sharing.KEY_TITLE";
+ field public static final String KEY_URIS = "androidx.browser.trusted.sharing.KEY_URIS";
+ field public final String? text;
+ field public final String? title;
+ field public final java.util.List<android.net.Uri!>? uris;
+ }
+
+ public final class ShareTarget {
+ ctor public ShareTarget(String, @androidx.browser.trusted.sharing.ShareTarget.RequestMethod String?, @androidx.browser.trusted.sharing.ShareTarget.EncodingType String?, androidx.browser.trusted.sharing.ShareTarget.Params);
+ method public static androidx.browser.trusted.sharing.ShareTarget? fromBundle(android.os.Bundle);
+ method public android.os.Bundle toBundle();
+ field public static final String ENCODING_TYPE_MULTIPART = "multipart/form-data";
+ field public static final String ENCODING_TYPE_URL_ENCODED = "application/x-www-form-urlencoded";
+ field public static final String KEY_ACTION = "androidx.browser.trusted.sharing.KEY_ACTION";
+ field public static final String KEY_ENCTYPE = "androidx.browser.trusted.sharing.KEY_ENCTYPE";
+ field public static final String KEY_METHOD = "androidx.browser.trusted.sharing.KEY_METHOD";
+ field public static final String KEY_PARAMS = "androidx.browser.trusted.sharing.KEY_PARAMS";
+ field public static final String METHOD_GET = "GET";
+ field public static final String METHOD_POST = "POST";
+ field public final String action;
+ field @androidx.browser.trusted.sharing.ShareTarget.EncodingType public final String? encodingType;
+ field @androidx.browser.trusted.sharing.ShareTarget.RequestMethod public final String? method;
+ field public final androidx.browser.trusted.sharing.ShareTarget.Params params;
+ }
+
+
+ public static class ShareTarget.FileFormField {
+ ctor public ShareTarget.FileFormField(String, java.util.List<java.lang.String!>);
+ field public static final String KEY_ACCEPTED_TYPES = "androidx.browser.trusted.sharing.KEY_ACCEPTED_TYPES";
+ field public static final String KEY_NAME = "androidx.browser.trusted.sharing.KEY_FILE_NAME";
+ field public final java.util.List<java.lang.String!> acceptedTypes;
+ field public final String name;
+ }
+
+ public static class ShareTarget.Params {
+ ctor public ShareTarget.Params(String?, String?, java.util.List<androidx.browser.trusted.sharing.ShareTarget.FileFormField!>?);
+ field public static final String KEY_FILES = "androidx.browser.trusted.sharing.KEY_FILES";
+ field public static final String KEY_TEXT = "androidx.browser.trusted.sharing.KEY_TEXT";
+ field public static final String KEY_TITLE = "androidx.browser.trusted.sharing.KEY_TITLE";
+ field public final java.util.List<androidx.browser.trusted.sharing.ShareTarget.FileFormField!>? files;
+ field public final String? text;
+ field public final String? title;
+ }
+
+
+}
+
package androidx.browser.trusted.splashscreens {
public final class SplashScreenParamKey {
diff --git a/browser/api/restricted_current.txt b/browser/api/restricted_current.txt
index 1e2b761..ae6035ea 100644
--- a/browser/api/restricted_current.txt
+++ b/browser/api/restricted_current.txt
@@ -275,9 +275,12 @@
method public androidx.browser.trusted.TrustedWebActivityIntentBuilder setColorScheme(int);
method public androidx.browser.trusted.TrustedWebActivityIntentBuilder setColorSchemeParams(int, androidx.browser.customtabs.CustomTabColorSchemeParams);
method public androidx.browser.trusted.TrustedWebActivityIntentBuilder setNavigationBarColor(@ColorInt int);
+ method public androidx.browser.trusted.TrustedWebActivityIntentBuilder setShareParams(androidx.browser.trusted.sharing.ShareTarget, androidx.browser.trusted.sharing.ShareData);
method public androidx.browser.trusted.TrustedWebActivityIntentBuilder setSplashScreenParams(android.os.Bundle);
method public androidx.browser.trusted.TrustedWebActivityIntentBuilder setToolbarColor(@ColorInt int);
field public static final String EXTRA_ADDITIONAL_TRUSTED_ORIGINS = "android.support.customtabs.extra.ADDITIONAL_TRUSTED_ORIGINS";
+ field public static final String EXTRA_SHARE_DATA = "androidx.browser.trusted.extra.SHARE_DATA";
+ field public static final String EXTRA_SHARE_TARGET = "androidx.browser.trusted.extra.SHARE_TARGET";
field public static final String EXTRA_SPLASH_SCREEN_PARAMS = "androidx.browser.trusted.EXTRA_SPLASH_SCREEN_PARAMS";
}
@@ -320,6 +323,60 @@
}
+package androidx.browser.trusted.sharing {
+
+ public final class ShareData {
+ ctor public ShareData(String?, String?, java.util.List<android.net.Uri!>?);
+ method public static androidx.browser.trusted.sharing.ShareData fromBundle(android.os.Bundle);
+ method public android.os.Bundle toBundle();
+ field public static final String KEY_TEXT = "androidx.browser.trusted.sharing.KEY_TEXT";
+ field public static final String KEY_TITLE = "androidx.browser.trusted.sharing.KEY_TITLE";
+ field public static final String KEY_URIS = "androidx.browser.trusted.sharing.KEY_URIS";
+ field public final String? text;
+ field public final String? title;
+ field public final java.util.List<android.net.Uri!>? uris;
+ }
+
+ public final class ShareTarget {
+ ctor public ShareTarget(String, @androidx.browser.trusted.sharing.ShareTarget.RequestMethod String?, @androidx.browser.trusted.sharing.ShareTarget.EncodingType String?, androidx.browser.trusted.sharing.ShareTarget.Params);
+ method public static androidx.browser.trusted.sharing.ShareTarget? fromBundle(android.os.Bundle);
+ method public android.os.Bundle toBundle();
+ field public static final String ENCODING_TYPE_MULTIPART = "multipart/form-data";
+ field public static final String ENCODING_TYPE_URL_ENCODED = "application/x-www-form-urlencoded";
+ field public static final String KEY_ACTION = "androidx.browser.trusted.sharing.KEY_ACTION";
+ field public static final String KEY_ENCTYPE = "androidx.browser.trusted.sharing.KEY_ENCTYPE";
+ field public static final String KEY_METHOD = "androidx.browser.trusted.sharing.KEY_METHOD";
+ field public static final String KEY_PARAMS = "androidx.browser.trusted.sharing.KEY_PARAMS";
+ field public static final String METHOD_GET = "GET";
+ field public static final String METHOD_POST = "POST";
+ field public final String action;
+ field @androidx.browser.trusted.sharing.ShareTarget.EncodingType public final String? encodingType;
+ field @androidx.browser.trusted.sharing.ShareTarget.RequestMethod public final String? method;
+ field public final androidx.browser.trusted.sharing.ShareTarget.Params params;
+ }
+
+
+ public static class ShareTarget.FileFormField {
+ ctor public ShareTarget.FileFormField(String, java.util.List<java.lang.String!>);
+ field public static final String KEY_ACCEPTED_TYPES = "androidx.browser.trusted.sharing.KEY_ACCEPTED_TYPES";
+ field public static final String KEY_NAME = "androidx.browser.trusted.sharing.KEY_FILE_NAME";
+ field public final java.util.List<java.lang.String!> acceptedTypes;
+ field public final String name;
+ }
+
+ public static class ShareTarget.Params {
+ ctor public ShareTarget.Params(String?, String?, java.util.List<androidx.browser.trusted.sharing.ShareTarget.FileFormField!>?);
+ field public static final String KEY_FILES = "androidx.browser.trusted.sharing.KEY_FILES";
+ field public static final String KEY_TEXT = "androidx.browser.trusted.sharing.KEY_TEXT";
+ field public static final String KEY_TITLE = "androidx.browser.trusted.sharing.KEY_TITLE";
+ field public final java.util.List<androidx.browser.trusted.sharing.ShareTarget.FileFormField!>? files;
+ field public final String? text;
+ field public final String? title;
+ }
+
+
+}
+
package androidx.browser.trusted.splashscreens {
public final class SplashScreenParamKey {
diff --git a/browser/build.gradle b/browser/build.gradle
index 06018de..5dd3789 100644
--- a/browser/build.gradle
+++ b/browser/build.gradle
@@ -17,7 +17,7 @@
}
dependencies {
- api("androidx.core:core:1.1.0-rc01")
+ api("androidx.core:core:1.1.0")
api("androidx.annotation:annotation:1.1.0")
implementation("androidx.collection:collection:1.1.0")
diff --git a/browser/src/main/java/androidx/browser/customtabs/CustomTabsSessionToken.java b/browser/src/main/java/androidx/browser/customtabs/CustomTabsSessionToken.java
index 6dc60c9..44810bf 100644
--- a/browser/src/main/java/androidx/browser/customtabs/CustomTabsSessionToken.java
+++ b/browser/src/main/java/androidx/browser/customtabs/CustomTabsSessionToken.java
@@ -103,6 +103,11 @@
CustomTabsSessionToken(@Nullable ICustomTabsCallback callbackBinder,
@Nullable PendingIntent sessionId) {
+ if (callbackBinder == null && sessionId == null) {
+ throw new IllegalStateException("CustomTabsSessionToken must have either a session id "
+ + "or a callback (or both).");
+ }
+
mCallbackBinder = callbackBinder;
mSessionId = sessionId;
@@ -193,10 +198,16 @@
public boolean equals(Object o) {
if (!(o instanceof CustomTabsSessionToken)) return false;
CustomTabsSessionToken other = (CustomTabsSessionToken) o;
- if (mSessionId != null && other.getId() != null) return mSessionId.equals(other.getId());
- return other.getCallbackBinder() != null
- && other.getCallbackBinder().equals(mCallbackBinder.asBinder());
+ PendingIntent otherSessionId = other.getId();
+ // If one object has a session id and the other one doesn't, they're not equal.
+ if ((mSessionId == null) != (otherSessionId == null)) return false;
+
+ // If both objects have an id, check that they are equal.
+ if (mSessionId != null) return mSessionId.equals(otherSessionId);
+
+ // Otherwise check for binder equality.
+ return getCallbackBinder().equals(other.getCallbackBinder());
}
/**
diff --git a/browser/src/main/java/androidx/browser/trusted/TrustedWebActivityIntent.java b/browser/src/main/java/androidx/browser/trusted/TrustedWebActivityIntent.java
index d04fb03..3f29c2b 100644
--- a/browser/src/main/java/androidx/browser/trusted/TrustedWebActivityIntent.java
+++ b/browser/src/main/java/androidx/browser/trusted/TrustedWebActivityIntent.java
@@ -18,10 +18,13 @@
import android.content.Context;
import android.content.Intent;
+import android.net.Uri;
import androidx.annotation.NonNull;
import androidx.core.content.ContextCompat;
+import java.util.List;
+
/**
* Holds an {@link Intent} and other data necessary to start a Trusted Web Activity.
*/
@@ -29,17 +32,28 @@
@NonNull
private final Intent mIntent;
- TrustedWebActivityIntent(@NonNull Intent intent) {
+ @NonNull
+ private final List<Uri> mSharedFileUris;
+
+ TrustedWebActivityIntent(@NonNull Intent intent, @NonNull List<Uri> sharedFileUris) {
mIntent = intent;
+ mSharedFileUris = sharedFileUris;
}
/**
* Launches a Trusted Web Activity.
*/
public void launchTrustedWebActivity(@NonNull Context context) {
+ grantUriPermissionToProvider(context);
ContextCompat.startActivity(context, mIntent, null);
}
+ private void grantUriPermissionToProvider(Context context) {
+ for (Uri uri : mSharedFileUris) {
+ context.grantUriPermission(mIntent.getPackage(), uri,
+ Intent.FLAG_GRANT_READ_URI_PERMISSION);
+ }
+ }
/**
* Returns the held {@link Intent}. For launching a Trusted Web Activity prefer using
diff --git a/browser/src/main/java/androidx/browser/trusted/TrustedWebActivityIntentBuilder.java b/browser/src/main/java/androidx/browser/trusted/TrustedWebActivityIntentBuilder.java
index 8c0c98e..1364512 100644
--- a/browser/src/main/java/androidx/browser/trusted/TrustedWebActivityIntentBuilder.java
+++ b/browser/src/main/java/androidx/browser/trusted/TrustedWebActivityIntentBuilder.java
@@ -28,9 +28,12 @@
import androidx.browser.customtabs.CustomTabsIntent;
import androidx.browser.customtabs.CustomTabsSession;
import androidx.browser.customtabs.TrustedWebUtils;
+import androidx.browser.trusted.sharing.ShareData;
+import androidx.browser.trusted.sharing.ShareTarget;
import androidx.browser.trusted.splashscreens.SplashScreenParamKey;
import java.util.ArrayList;
+import java.util.Collections;
import java.util.List;
/**
@@ -60,12 +63,21 @@
public static final String EXTRA_ADDITIONAL_TRUSTED_ORIGINS =
"android.support.customtabs.extra.ADDITIONAL_TRUSTED_ORIGINS";
+ /** Extra for the share target, see {@link #setShareParams}. */
+ public static final String EXTRA_SHARE_TARGET = "androidx.browser.trusted.extra.SHARE_TARGET";
+
+ /** Extra for the share data, see {@link #setShareParams}. */
+ public static final String EXTRA_SHARE_DATA = "androidx.browser.trusted.extra.SHARE_DATA";
+
@NonNull private final Uri mUri;
@NonNull private final CustomTabsIntent.Builder mIntentBuilder = new CustomTabsIntent.Builder();
@Nullable private List<String> mAdditionalTrustedOrigins;
@Nullable private Bundle mSplashScreenParams;
+ @Nullable private ShareData mShareData;
+ @Nullable private ShareTarget mShareTarget;
+
/**
* Creates a Builder given the required parameters.
* @param uri The web page to launch as Trusted Web Activity.
@@ -165,6 +177,21 @@
}
/**
+ * Sets the parameters for delivering data to a Web Share Target via a Trusted Web Activity.
+ *
+ * @param shareTarget A {@link ShareTarget} object describing the Web Share Target.
+ * @param shareData A {@link ShareData} object containing the data to be sent to the Web Share
+ * Target.
+ */
+ @NonNull
+ public TrustedWebActivityIntentBuilder setShareParams(@NonNull ShareTarget shareTarget,
+ @NonNull ShareData shareData) {
+ mShareTarget = shareTarget;
+ mShareData = shareData;
+ return this;
+ }
+
+ /**
* Builds an instance of {@link TrustedWebActivityIntent].
*
* @param session The {@link CustomTabsSession} to use for launching a Trusted Web Activity.
@@ -187,7 +214,15 @@
if (mSplashScreenParams != null) {
intent.putExtra(EXTRA_SPLASH_SCREEN_PARAMS, mSplashScreenParams);
}
- return new TrustedWebActivityIntent(intent);
+ List<Uri> sharedUris = Collections.emptyList();
+ if (mShareTarget != null && mShareData != null) {
+ intent.putExtra(EXTRA_SHARE_TARGET, mShareTarget.toBundle());
+ intent.putExtra(EXTRA_SHARE_DATA, mShareData.toBundle());
+ if (mShareData.uris != null) {
+ sharedUris = mShareData.uris;
+ }
+ }
+ return new TrustedWebActivityIntent(intent, sharedUris);
}
/**
diff --git a/browser/src/main/java/androidx/browser/trusted/sharing/ShareData.java b/browser/src/main/java/androidx/browser/trusted/sharing/ShareData.java
new file mode 100644
index 0000000..c74ab4c
--- /dev/null
+++ b/browser/src/main/java/androidx/browser/trusted/sharing/ShareData.java
@@ -0,0 +1,80 @@
+/*
+ * 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.browser.trusted.sharing;
+
+import android.net.Uri;
+import android.os.Bundle;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Contains data to be delivered to a Web Share Target via a Trusted Web Activity.
+ * See {@link androidx.browser.trusted.TrustedWebActivityIntentBuilder#setShareParams}.
+ */
+public final class ShareData {
+ /** Bundle key for {@link #title}. */
+ public static final String KEY_TITLE = "androidx.browser.trusted.sharing.KEY_TITLE";
+
+ /** Bundle key for {@link #text}. */
+ public static final String KEY_TEXT = "androidx.browser.trusted.sharing.KEY_TEXT";
+
+ /** Bundle key for {@link #uris}. */
+ public static final String KEY_URIS = "androidx.browser.trusted.sharing.KEY_URIS";
+
+ /** Title of the shared message. */
+ @Nullable
+ public final String title;
+
+ /** Text of the shared message. */
+ @Nullable
+ public final String text;
+
+ /** URIs of files to be shared. */
+ @Nullable
+ public final List<Uri> uris;
+
+ /** Constructor. */
+ public ShareData(@Nullable String title, @Nullable String text, @Nullable List<Uri> uris) {
+ this.title = title;
+ this.text = text;
+ this.uris = uris;
+ }
+
+ /** Packs the object into a {@link Bundle} */
+ @NonNull
+ public Bundle toBundle() {
+ Bundle bundle = new Bundle();
+ bundle.putString(KEY_TITLE, title);
+ bundle.putString(KEY_TEXT, text);
+ if (uris != null) {
+ bundle.putParcelableArrayList(KEY_URIS, new ArrayList<>(uris));
+ }
+ return bundle;
+ }
+
+ /** Unpacks the object from a {@link Bundle}. */
+ @NonNull
+ public static ShareData fromBundle(@NonNull Bundle bundle) {
+ return new ShareData(bundle.getString(KEY_TITLE),
+ bundle.getString(KEY_TEXT),
+ bundle.getParcelableArrayList(KEY_URIS));
+ }
+}
diff --git a/browser/src/main/java/androidx/browser/trusted/sharing/ShareTarget.java b/browser/src/main/java/androidx/browser/trusted/sharing/ShareTarget.java
new file mode 100644
index 0000000..acc05ff
--- /dev/null
+++ b/browser/src/main/java/androidx/browser/trusted/sharing/ShareTarget.java
@@ -0,0 +1,270 @@
+/*
+ * 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.browser.trusted.sharing;
+
+import android.annotation.SuppressLint;
+import android.os.Bundle;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.annotation.RestrictTo;
+import androidx.annotation.StringDef;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Describes a Web Share Target associated with a Trusted Web Activity.
+ *
+ * The structure of a ShareTarget object follows the specification [1] of the "share_target" object
+ * within web manifest json, with the following exceptions:
+ * - The "action" field specifies the full URL of the Share Target, and not only the path.
+ * - There is no "url" field in the "params" object, since urls are not supplied separately from
+ * text in Android's ACTION_SEND and ACTION_SEND_MULTIPLE intents.
+ *
+ * [1] https://wicg.github.io/web-share-target/level-2/
+ */
+public final class ShareTarget {
+
+ /** Bundle key for {@link #action}. */
+ @SuppressLint("IntentName")
+ public static final String KEY_ACTION = "androidx.browser.trusted.sharing.KEY_ACTION";
+
+ /** Bundle key for {@link #method}. */
+ public static final String KEY_METHOD = "androidx.browser.trusted.sharing.KEY_METHOD";
+
+ /** Bundle key for {@link #encodingType}. */
+ public static final String KEY_ENCTYPE = "androidx.browser.trusted.sharing.KEY_ENCTYPE";
+
+ /** Bundle key for {@link #params}. */
+ public static final String KEY_PARAMS = "androidx.browser.trusted.sharing.KEY_PARAMS";
+
+ /** @hide */
+ @RestrictTo(RestrictTo.Scope.LIBRARY)
+ @StringDef({METHOD_GET, METHOD_POST})
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface RequestMethod {}
+
+ /** See {@link #method}. */
+ public static final String METHOD_GET = "GET";
+
+ /** See {@link #method}. */
+ public static final String METHOD_POST = "POST";
+
+ /** @hide */
+ @RestrictTo(RestrictTo.Scope.LIBRARY)
+ @StringDef({ENCODING_TYPE_URL_ENCODED, ENCODING_TYPE_MULTIPART})
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface EncodingType {}
+
+ /** See {@link #encodingType} */
+ public static final String ENCODING_TYPE_URL_ENCODED = "application/x-www-form-urlencoded";
+
+ /** See {@link #encodingType} */
+ public static final String ENCODING_TYPE_MULTIPART = "multipart/form-data";
+
+ /**
+ * URL of the Web Share Target. Must belong to an origin associated with the Trusted Web
+ * Activity. For example, assuming the origin is "https://mypwa.com", the action could be
+ * "https://mypwa.com/share.html".
+ */
+ @NonNull
+ public final String action;
+
+ /**
+ * HTTP request method for the Web Share Target. Must be {@link #METHOD_GET} or
+ * {@link #METHOD_POST}. Default is {@link #METHOD_GET}.
+ */
+ @Nullable
+ @RequestMethod
+ public final String method;
+
+ /**
+ * Specifies how the shared data should be encoded in the body of a POST request. Must be
+ * {@link #ENCODING_TYPE_MULTIPART} or {@link #ENCODING_TYPE_URL_ENCODED}. Default is
+ * {@link #ENCODING_TYPE_URL_ENCODED}.
+ */
+ @Nullable
+ @EncodingType
+ public final String encodingType;
+
+ /**
+ * Contains the parameter names, see {@link Params}.
+ */
+ @NonNull
+ public final Params params;
+
+ /** Constructor, see field descriptions above. */
+ public ShareTarget(@NonNull String action, @Nullable @RequestMethod String method,
+ @Nullable @EncodingType String encodingType, @NonNull Params params) {
+ this.action = action;
+ this.method = method;
+ this.encodingType = encodingType;
+ this.params = params;
+ }
+
+ /** Packs the object into a {@link Bundle}. */
+ @NonNull
+ public Bundle toBundle() {
+ Bundle bundle = new Bundle();
+ bundle.putString(KEY_ACTION, action);
+ bundle.putString(KEY_METHOD, method);
+ bundle.putString(KEY_ENCTYPE, encodingType);
+ bundle.putBundle(KEY_PARAMS, params.toBundle());
+ return bundle;
+ }
+
+ /** Unpacks the object from a {@link Bundle}. */
+ @Nullable
+ public static ShareTarget fromBundle(@NonNull Bundle bundle) {
+ String action = bundle.getString(KEY_ACTION);
+ String method = bundle.getString(KEY_METHOD);
+ String encType = bundle.getString(KEY_ENCTYPE);
+ Params params = Params.fromBundle(bundle.getBundle(KEY_PARAMS));
+ if (action == null || params == null) {
+ return null;
+ }
+ return new ShareTarget(action, method, encType, params);
+ }
+
+ /** Contains parameter names to be used for the data being shared. */
+ public static class Params {
+ /** Bundle key for {@link #title}. */
+ public static final String KEY_TITLE = "androidx.browser.trusted.sharing.KEY_TITLE";
+
+ /** Bundle key for {@link #text}. */
+ public static final String KEY_TEXT = "androidx.browser.trusted.sharing.KEY_TEXT";
+
+ /** Bundle key for {@link #files}. */
+ public static final String KEY_FILES = "androidx.browser.trusted.sharing.KEY_FILES";
+
+ /** The name of the query parameter used for the title of the message being shared. */
+ @Nullable
+ public final String title;
+
+ /** The name of the query parameter used for the body of the message being shared. */
+ @Nullable
+ public final String text;
+
+ /**
+ * Defines form fields for the files being shared, see {@link FileFormField}.
+ * Web Share Target can have multiple form fields associated with different MIME types.
+ * If a file passes the MIME type filters of several {@link FileFormField}s,
+ * the one that has the lowest index in this list is picked; see [1] for details.
+ *
+ * [1] https://wicg.github.io/web-share-target/level-2/#launching-the-web-share-target
+ */
+ @Nullable
+ public final List<FileFormField> files;
+
+ /** Constructor, see field descriptions above. */
+ public Params(@Nullable String title, @Nullable String text,
+ @Nullable List<FileFormField> files) {
+ this.title = title;
+ this.text = text;
+ this.files = files;
+ }
+
+ @SuppressWarnings("WeakerAccess") /* synthetic access */
+ @NonNull
+ Bundle toBundle() {
+ Bundle bundle = new Bundle();
+ bundle.putString(KEY_TITLE, title);
+ bundle.putString(KEY_TEXT, text);
+ if (files != null) {
+ ArrayList<Bundle> fileBundles = new ArrayList<>();
+ for (FileFormField file : files) {
+ fileBundles.add(file.toBundle());
+ }
+ bundle.putParcelableArrayList(KEY_FILES, fileBundles);
+ }
+
+ return bundle;
+ }
+
+ @SuppressWarnings("WeakerAccess") /* synthetic access */
+ @Nullable
+ static Params fromBundle(@Nullable Bundle bundle) {
+ if (bundle == null) {
+ return null;
+ }
+ List<FileFormField> files = null;
+ List<Bundle> fileBundles = bundle.getParcelableArrayList(KEY_FILES);
+ if (fileBundles != null) {
+ files = new ArrayList<>();
+ for (Bundle fileBundle : fileBundles) {
+ files.add(FileFormField.fromBundle(fileBundle));
+ }
+ }
+ return new Params(bundle.getString(KEY_TITLE), bundle.getString(KEY_TEXT),
+ files);
+ }
+ }
+
+ /** Defines a form field for sharing files. */
+ public static class FileFormField {
+ /** Bundle key for {@link #name}. */
+ public static final String KEY_NAME = "androidx.browser.trusted.sharing.KEY_FILE_NAME";
+
+ /** Bundle key for {@link #acceptedTypes}. */
+ public static final String KEY_ACCEPTED_TYPES =
+ "androidx.browser.trusted.sharing.KEY_ACCEPTED_TYPES";
+
+ /** Name of the form field. */
+ @NonNull
+ public final String name;
+
+ /**
+ * List of MIME types or file extensions to be sent in this field. The MIME type matching
+ * algorithm is specified by
+ * https://wicg.github.io/web-share-target/level-2/#determining-if-a-file-is-accepted.
+ */
+ @NonNull
+ public final List<String> acceptedTypes;
+
+ /** Constructor, see field descriptions above. */
+ public FileFormField(@NonNull String name, @NonNull List<String> acceptedTypes) {
+ this.name = name;
+ this.acceptedTypes = acceptedTypes;
+ }
+
+ @SuppressWarnings("WeakerAccess") /* synthetic access */
+ @NonNull
+ Bundle toBundle() {
+ Bundle bundle = new Bundle();
+ bundle.putString(KEY_NAME, name);
+ bundle.putStringArrayList(KEY_ACCEPTED_TYPES, new ArrayList<>(acceptedTypes));
+ return bundle;
+ }
+
+ @SuppressWarnings("WeakerAccess") /* synthetic access */
+ @Nullable
+ static FileFormField fromBundle(@Nullable Bundle bundle) {
+ if (bundle == null) {
+ return null;
+ }
+ String name = bundle.getString(KEY_NAME);
+ ArrayList<String> acceptedTypes = bundle.getStringArrayList(KEY_ACCEPTED_TYPES);
+ if (name == null || acceptedTypes == null) {
+ return null;
+ }
+ return new FileFormField(name, acceptedTypes);
+ }
+ }
+}
diff --git a/browser/src/test/java/androidx/browser/customtabs/CustomTabsSessionTokenTest.java b/browser/src/test/java/androidx/browser/customtabs/CustomTabsSessionTokenTest.java
new file mode 100644
index 0000000..b64cd51
--- /dev/null
+++ b/browser/src/test/java/androidx/browser/customtabs/CustomTabsSessionTokenTest.java
@@ -0,0 +1,127 @@
+/*
+ * 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.browser.customtabs;
+
+import android.app.PendingIntent;
+import android.content.Context;
+import android.content.Intent;
+import android.support.customtabs.ICustomTabsCallback;
+
+import androidx.test.core.app.ApplicationProvider;
+import androidx.test.filters.SmallTest;
+
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.robolectric.RobolectricTestRunner;
+import org.robolectric.annotation.internal.DoNotInstrument;
+
+/**
+ * Tests for {@link CustomTabsSessionToken}.
+ */
+@RunWith(RobolectricTestRunner.class)
+@DoNotInstrument
+@SmallTest
+public class CustomTabsSessionTokenTest {
+ private Context mContext;
+
+ @Before
+ public void setup() {
+ mContext = ApplicationProvider.getApplicationContext();
+ }
+
+ @Test
+ public void testEquality_withId() {
+ CustomTabsSessionToken token1 = new CustomTabsSessionToken(
+ new CustomTabsSessionToken.MockCallback(),
+ createSessionId(27)
+ );
+
+ CustomTabsSessionToken token2 = new CustomTabsSessionToken(
+ new CustomTabsSessionToken.MockCallback(),
+ createSessionId(27)
+ );
+
+ assertEquals(token1, token2);
+ }
+
+ @Test
+ public void testNonEquality_withId() {
+ // Using the same binder to ensure only the id matters.
+ ICustomTabsCallback.Stub binder = new CustomTabsSessionToken.MockCallback();
+
+ CustomTabsSessionToken token1 = new CustomTabsSessionToken(binder, createSessionId(10));
+ CustomTabsSessionToken token2 = new CustomTabsSessionToken(binder, createSessionId(20));
+
+ assertNotEquals(token1, token2);
+ }
+
+ @Test
+ public void testEquality_withBinder() {
+ ICustomTabsCallback.Stub binder = new CustomTabsSessionToken.MockCallback();
+
+ CustomTabsSessionToken token1 = new CustomTabsSessionToken(binder, null);
+ CustomTabsSessionToken token2 = new CustomTabsSessionToken(binder, null);
+
+ assertEquals(token1, token2);
+ }
+
+ @Test
+ public void testNonEquality_withBinder() {
+ ICustomTabsCallback.Stub binder1 = new CustomTabsSessionToken.MockCallback();
+ ICustomTabsCallback.Stub binder2 = new CustomTabsSessionToken.MockCallback();
+
+ CustomTabsSessionToken token1 = new CustomTabsSessionToken(binder1, null);
+ CustomTabsSessionToken token2 = new CustomTabsSessionToken(binder2, null);
+
+ assertNotEquals(token1, token2);
+ }
+
+ @Test
+ public void testNonEquality_mixedIdAndBinder() {
+ // Using the same binder to ensure only the id matters.
+ ICustomTabsCallback.Stub binder = new CustomTabsSessionToken.MockCallback();
+
+ CustomTabsSessionToken token1 = new CustomTabsSessionToken(binder, createSessionId(10));
+ // Tokens cannot be mixed if only one has an id even if the binder is the same.
+ CustomTabsSessionToken token2 = new CustomTabsSessionToken(binder, null);
+
+ assertNotEquals(token1, token2);
+ }
+
+ // This code does the same as CustomTabsClient#createSessionId but that is not necessary for the
+ // test, we just need to create a PendingIntent that uses sessionId as the requestCode.
+ private PendingIntent createSessionId(int sessionId) {
+ return PendingIntent.getActivity(mContext, sessionId, new Intent(), 0);
+ }
+
+ private void assertEquals(CustomTabsSessionToken token1, CustomTabsSessionToken token2) {
+ Assert.assertEquals(token1, token2);
+ Assert.assertEquals(token2, token1);
+
+ Assert.assertEquals(token1.hashCode(), token2.hashCode());
+ }
+
+ private void assertNotEquals(CustomTabsSessionToken token1, CustomTabsSessionToken token2) {
+ Assert.assertNotEquals(token1, token2);
+ Assert.assertNotEquals(token2, token1);
+
+ // I guess technically this could be flaky, but let's hope not...
+ Assert.assertNotEquals(token1.hashCode(), token2.hashCode());
+ }
+}
diff --git a/browser/src/test/java/androidx/browser/trusted/TrustedWebActivityIntentBuilderTest.java b/browser/src/test/java/androidx/browser/trusted/TrustedWebActivityIntentBuilderTest.java
index b145abb..bdfdfa0 100644
--- a/browser/src/test/java/androidx/browser/trusted/TrustedWebActivityIntentBuilderTest.java
+++ b/browser/src/test/java/androidx/browser/trusted/TrustedWebActivityIntentBuilderTest.java
@@ -31,6 +31,8 @@
import androidx.browser.customtabs.CustomTabsIntent;
import androidx.browser.customtabs.CustomTabsSession;
import androidx.browser.customtabs.TestUtil;
+import androidx.browser.trusted.sharing.ShareData;
+import androidx.browser.trusted.sharing.ShareTarget;
import androidx.browser.trusted.splashscreens.SplashScreenParamKey;
import androidx.test.filters.SmallTest;
@@ -63,12 +65,15 @@
Bundle splashScreenParams = new Bundle();
int splashBgColor = 0x112233;
- splashScreenParams.putInt(
- SplashScreenParamKey.BACKGROUND_COLOR, splashBgColor);
+ splashScreenParams.putInt(SplashScreenParamKey.BACKGROUND_COLOR, splashBgColor);
CustomTabColorSchemeParams colorSchemeParams = new CustomTabColorSchemeParams.Builder()
.setToolbarColor(0xff112233).build();
+ ShareData shareData = new ShareData("share_title", "share_text", null);
+ ShareTarget shareTarget = new ShareTarget("action", null, null,
+ new ShareTarget.Params(null, null, null));
+
CustomTabsSession session = TestUtil.makeMockSession();
Intent intent = new TrustedWebActivityIntentBuilder(url)
@@ -78,6 +83,7 @@
.setColorSchemeParams(COLOR_SCHEME_DARK, colorSchemeParams)
.setAdditionalTrustedOrigins(additionalTrustedOrigins)
.setSplashScreenParams(splashScreenParams)
+ .setShareParams(shareTarget, shareData)
.build(session)
.getIntent();
@@ -100,5 +106,15 @@
// No need to test every splash screen param: they are sent in as-is in provided Bundle.
assertEquals(splashBgColor, splashScreenParamsReceived.getInt(
SplashScreenParamKey.BACKGROUND_COLOR));
+
+ ShareData shareDataFromIntent = ShareData.fromBundle(intent.getBundleExtra(
+ TrustedWebActivityIntentBuilder.EXTRA_SHARE_DATA));
+ // Bundling-unbundling of the ShareData and ShareTarget is tested in more detail elsewhere.
+ // Here we only check that the Builder correctly added the extras.
+ assertEquals(shareData.title, shareDataFromIntent.title);
+
+ ShareTarget shareTargetFromIntent = ShareTarget.fromBundle(intent.getBundleExtra(
+ TrustedWebActivityIntentBuilder.EXTRA_SHARE_TARGET));
+ assertEquals(shareTarget.action, shareTargetFromIntent.action);
}
}
diff --git a/browser/src/test/java/androidx/browser/trusted/sharing/ShareDataTest.java b/browser/src/test/java/androidx/browser/trusted/sharing/ShareDataTest.java
new file mode 100644
index 0000000..c239fe8
--- /dev/null
+++ b/browser/src/test/java/androidx/browser/trusted/sharing/ShareDataTest.java
@@ -0,0 +1,70 @@
+/*
+ * 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.browser.trusted.sharing;
+
+import static org.junit.Assert.assertEquals;
+
+import android.net.Uri;
+
+import androidx.test.filters.SmallTest;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.robolectric.ParameterizedRobolectricTestRunner;
+import org.robolectric.annotation.internal.DoNotInstrument;
+
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.List;
+
+/**
+ * Tests for {@link ShareData}.
+ */
+@RunWith(ParameterizedRobolectricTestRunner.class)
+@DoNotInstrument
+@SmallTest
+public class ShareDataTest {
+
+ private static final List<Uri> URIS = Arrays.asList(Uri.parse("http://foo"),
+ Uri.parse("http://bar"));
+
+ @ParameterizedRobolectricTestRunner.Parameters(name = "{1}")
+ public static Collection<Object[]> parameters() {
+ return Arrays.asList(
+ new Object[]{new ShareData("foo", "bar", URIS), "All included"},
+ new Object[]{new ShareData("foo", "bar", null), "No URIs"},
+ new Object[]{new ShareData(null, "bar", URIS), "No title"},
+ new Object[]{new ShareData("foo", null, URIS), "No text"});
+ }
+
+ private final ShareData mShareData;
+
+ public ShareDataTest(ShareData shareData, String testName) {
+ mShareData = shareData;
+ }
+
+ @Test
+ public void bundlingAndUnbundlingYieldsOriginalObject() {
+ assertShareDataEquals(mShareData, ShareData.fromBundle(mShareData.toBundle()));
+ }
+
+ private void assertShareDataEquals(ShareData expected, ShareData actual) {
+ assertEquals(expected.title, actual.title);
+ assertEquals(expected.text, actual.text);
+ assertEquals(expected.uris, actual.uris);
+ }
+}
diff --git a/browser/src/test/java/androidx/browser/trusted/sharing/ShareTargetTest.java b/browser/src/test/java/androidx/browser/trusted/sharing/ShareTargetTest.java
new file mode 100644
index 0000000..bc59dd6
--- /dev/null
+++ b/browser/src/test/java/androidx/browser/trusted/sharing/ShareTargetTest.java
@@ -0,0 +1,120 @@
+/*
+ * 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.browser.trusted.sharing;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
+
+import androidx.browser.trusted.sharing.ShareTarget.FileFormField;
+import androidx.browser.trusted.sharing.ShareTarget.Params;
+import androidx.test.filters.SmallTest;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.robolectric.ParameterizedRobolectricTestRunner;
+import org.robolectric.annotation.internal.DoNotInstrument;
+
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.List;
+
+/**
+ * Tests for {@link ShareTarget}.
+ */
+@RunWith(ParameterizedRobolectricTestRunner.class)
+@DoNotInstrument
+@SmallTest
+public class ShareTargetTest {
+
+ private static final String ACTION = "action.html";
+ private static final String METHOD = "POST";
+ private static final String ENCODING_TYPE = "multipart/form-data";
+
+ private static final FileFormField FILE_FIELD_1 =
+ new FileFormField("file1", Arrays.asList("type1", "type2"));
+
+ private static final FileFormField FILE_FIELD_2 =
+ new FileFormField("file2", Arrays.asList("type3", "type4"));
+
+ private static final List<FileFormField> FILES =
+ Arrays.asList(FILE_FIELD_1, FILE_FIELD_2);
+
+ private static final Params DEFAULT_PARAMS = new Params("title", "text", FILES);
+ private static final Params PARAMS_NO_FILES = new Params("title", "text", null);
+ private static final Params PARAMS_NO_TITLE = new Params(null, "text", FILES);
+ private static final Params PARAMS_NO_TEXT = new Params("title", null, FILES);
+
+ @ParameterizedRobolectricTestRunner.Parameters(name = "{1}")
+ public static Collection<Object[]> parameters() {
+ return Arrays.asList(
+ new Object[]{new ShareTarget(ACTION, METHOD, ENCODING_TYPE, DEFAULT_PARAMS),
+ "All included"},
+ new Object[]{new ShareTarget(ACTION, METHOD, ENCODING_TYPE, PARAMS_NO_FILES),
+ "No files"},
+ new Object[]{new ShareTarget(ACTION, METHOD, ENCODING_TYPE, PARAMS_NO_TITLE),
+ "No title"},
+ new Object[]{new ShareTarget(ACTION, METHOD, ENCODING_TYPE, PARAMS_NO_TEXT),
+ "No text"},
+ new Object[]{new ShareTarget(ACTION, null, ENCODING_TYPE, DEFAULT_PARAMS),
+ "No method"},
+ new Object[]{new ShareTarget(ACTION, METHOD, null, DEFAULT_PARAMS),
+ "No enc type"}
+ );
+ }
+
+ private final ShareTarget mShareTarget;
+
+ public ShareTargetTest(ShareTarget shareTarget, String testName) {
+ mShareTarget = shareTarget;
+ }
+
+ @Test
+ public void bundlingAndUnbundlingYieldsOriginalObject() {
+ assertShareTargetEquals(mShareTarget, ShareTarget.fromBundle(mShareTarget.toBundle()));
+ }
+
+ private void assertShareTargetEquals(ShareTarget expected, ShareTarget actual) {
+ assertEquals(expected.action, actual.action);
+ assertEquals(expected.encodingType, actual.encodingType);
+ assertEquals(expected.method, actual.method);
+ assertParamsEqual(expected.params, actual.params);
+ }
+
+ private void assertParamsEqual(Params expected, Params actual) {
+ assertEquals(expected.text, actual.text);
+ assertEquals(expected.title, actual.title);
+ assertFilesEqual(expected.files, actual.files);
+ }
+
+ private void assertFilesEqual(List<FileFormField> expected,
+ List<FileFormField> actual) {
+ if (expected == null) {
+ assertNull(actual);
+ return;
+ }
+ assertEquals(expected.size(), actual.size());
+ for (int i = 0; i < expected.size(); i++) {
+ assertFileEquals(expected.get(i), actual.get(i));
+ }
+ }
+
+ private void assertFileEquals(FileFormField expected,
+ FileFormField actual) {
+ assertEquals(expected.name, actual.name);
+ assertEquals(expected.acceptedTypes, actual.acceptedTypes);
+ }
+}
diff --git a/buildSrc-tests/src/test/kotlin/androidx/build/dependencyTracker/AffectedModuleDetectorImplTest.kt b/buildSrc-tests/src/test/kotlin/androidx/build/dependencyTracker/AffectedModuleDetectorImplTest.kt
index c84eebb..77e7421 100644
--- a/buildSrc-tests/src/test/kotlin/androidx/build/dependencyTracker/AffectedModuleDetectorImplTest.kt
+++ b/buildSrc-tests/src/test/kotlin/androidx/build/dependencyTracker/AffectedModuleDetectorImplTest.kt
@@ -16,6 +16,8 @@
package androidx.build.dependencyTracker
+import androidx.build.gitclient.Commit
+import androidx.build.gitclient.GitClient
import org.gradle.api.Project
import org.gradle.api.plugins.ExtraPropertiesExtension
import org.gradle.testfixtures.ProjectBuilder
@@ -833,5 +835,13 @@
) = changedFiles
override fun findPreviousMergeCL() = lastMergeSha
+
+ // Implement unused abstract method
+ override fun getGitLog(
+ sha: String,
+ top: String,
+ keepMerges: Boolean,
+ fullProjectDir: File
+ ): List<Commit> = listOf()
}
}
diff --git a/buildSrc-tests/src/test/kotlin/androidx/build/dependencyTracker/GitClientImplTest.kt b/buildSrc-tests/src/test/kotlin/androidx/build/dependencyTracker/GitClientImplTest.kt
index 6c6034f..768e4a7 100644
--- a/buildSrc-tests/src/test/kotlin/androidx/build/dependencyTracker/GitClientImplTest.kt
+++ b/buildSrc-tests/src/test/kotlin/androidx/build/dependencyTracker/GitClientImplTest.kt
@@ -16,8 +16,12 @@
package androidx.build.dependencyTracker
-import androidx.build.dependencyTracker.GitClientImpl.Companion.CHANGED_FILES_CMD_PREFIX
-import androidx.build.dependencyTracker.GitClientImpl.Companion.PREV_MERGE_CMD
+import androidx.build.gitclient.Commit
+import androidx.build.gitclient.CommitType
+import androidx.build.gitclient.GitClient
+import androidx.build.gitclient.GitClientImpl
+import androidx.build.gitclient.GitClientImpl.Companion.CHANGED_FILES_CMD_PREFIX
+import androidx.build.gitclient.GitClientImpl.Companion.PREV_MERGE_CMD
import junit.framework.TestCase.assertEquals
import junit.framework.TestCase.assertNull
import org.junit.Rule
@@ -33,10 +37,15 @@
val attachLogsRule = AttachLogsTestRule()
private val logger = attachLogsRule.logger
private val commandRunner = MockCommandRunner(logger)
+ /** The [GitClientImpl.workingDir] uses `System.getProperty("user.dir")` because the working
+ * directory passed to the [GitClientImpl] constructor needs to contain the a .git
+ * directory somewhere in the parent directory tree. @see [GitClientImpl]
+ */
private val client = GitClientImpl(
- workingDir = File("."),
- logger = logger,
- commandRunner = commandRunner)
+ workingDir = File(System.getProperty("user.dir")),
+ logger = logger,
+ commandRunner = commandRunner
+ )
@Test
fun findMerge() {
@@ -92,6 +101,316 @@
includeUncommitted = false))
}
+ @Test
+ fun parseAPICommitWithDefaultDelimiters() {
+ val commitWithApiChangeString: String =
+ """
+ _CommitStart
+ _CommitSHA:mySha
+ _Author:anemail@google.com
+ _Date:Tue Aug 6 15:05:55 2019 -0700
+ _Subject:Added a new API!
+ _Body:Also fixed some other bugs
+
+ Here is an explanation of my commit
+
+ Bug: 123456
+ Bug: b/1234567
+ Fixes: 123123
+ Test: ./gradlew buildOnServer
+
+ Change-Id: myChangeId
+
+ projectdir/a.java
+ projectdir/b.java
+ projectdir/androidTest/c.java
+ projectdir/api/some_api_file.txt
+ projectdir/api/current.txt
+ """
+ val commitWithApiChange: Commit = Commit(commitWithApiChangeString, "/projectdir/")
+ assertEquals("mySha", commitWithApiChange.sha)
+ assertEquals("anemail@google.com", commitWithApiChange.authorEmail)
+ assertEquals("myChangeId", commitWithApiChange.changeId)
+ assertEquals("Added a new API!", commitWithApiChange.summary)
+ assertEquals(CommitType.API_CHANGE, commitWithApiChange.type)
+ assertEquals(mutableListOf(123456, 1234567, 123123), commitWithApiChange.bugs)
+ assertEquals(
+ mutableListOf(
+ "projectdir/a.java",
+ "projectdir/b.java",
+ "projectdir/androidTest/c.java",
+ "projectdir/api/some_api_file.txt",
+ "projectdir/api/current.txt"
+ ),
+ commitWithApiChange.files
+ )
+ }
+
+ @Test
+ fun parseBugFixCommitWithCustomDelimiters() {
+ val commitSHADelimiter: String = "_MyCommitSHA:"
+ val subjectDelimiter: String = "_MySubject:"
+ val authorEmailDelimiter: String = "_MyAuthor:"
+ val projectDir: String = "group/artifact"
+ val commitWithABugFixString: String =
+ """
+ _CommitStart
+ ${commitSHADelimiter}mySha
+ ${authorEmailDelimiter}anemail@google.com
+ _Date:Tue Aug 6 15:05:55 2019 -0700
+ ${subjectDelimiter}Fixed a bug!
+ _Body:Also fixed some other bugs
+
+ Here is an explanation of my commit that changes a kotlin file
+
+ Bug: 111111, 222222
+ Test: ./gradlew buildOnServer
+
+ Change-Id: myChangeId
+
+ $projectDir/a.java
+ $projectDir/b.kt
+ $projectDir/androidTest/c.java
+ """
+ val commitWithABugFix: Commit = Commit(
+ commitWithABugFixString,
+ projectDir,
+ commitSHADelimiter = commitSHADelimiter,
+ subjectDelimiter = subjectDelimiter,
+ authorEmailDelimiter = authorEmailDelimiter
+ )
+ assertEquals("mySha", commitWithABugFix.sha)
+ assertEquals("anemail@google.com", commitWithABugFix.authorEmail)
+ assertEquals("myChangeId", commitWithABugFix.changeId)
+ assertEquals("Fixed a bug!", commitWithABugFix.summary)
+ assertEquals(CommitType.BUG_FIX, commitWithABugFix.type)
+ assertEquals(mutableListOf(111111, 222222), commitWithABugFix.bugs)
+ assertEquals(
+ mutableListOf(
+ "$projectDir/a.java",
+ "$projectDir/b.kt",
+ "$projectDir/androidTest/c.java"
+ ),
+ commitWithABugFix.files
+ )
+ }
+
+ @Test
+ fun parseExternalContributorCommitWithCustomDelimiters() {
+ val commitSHADelimiter: String = "_MyCommitSHA:"
+ val subjectDelimiter: String = "_MySubject:"
+ val authorEmailDelimiter: String = "_MyAuthor:"
+ val projectDir: String = "group/artifact"
+ val commitFromExternalContributorString: String =
+ """
+ _CommitStart
+ ${commitSHADelimiter}mySha
+ ${authorEmailDelimiter}externalcontributor@gmail.com
+ _Date:Thurs Aug 8 15:05:55 2019 -0700
+ ${subjectDelimiter}New compat API!
+ _Body:Also fixed some other bugs
+
+ Here is an explanation of my commit that changes a java file
+
+ Bug: 111111, 222222
+ Test: ./gradlew buildOnServer
+
+ Change-Id: myChangeId
+
+ $projectDir/a.java
+ $projectDir/b.java
+ $projectDir/androidTest/c.java
+ $projectDir/api/current.txt
+ $projectDir/api/1.2.0-alpha04.txt
+ """
+ val commitFromExternalContributor: Commit = Commit(
+ commitFromExternalContributorString,
+ projectDir,
+ commitSHADelimiter = commitSHADelimiter,
+ subjectDelimiter = subjectDelimiter,
+ authorEmailDelimiter = authorEmailDelimiter
+ )
+ assertEquals("mySha", commitFromExternalContributor.sha)
+ assertEquals("externalcontributor@gmail.com", commitFromExternalContributor.authorEmail)
+ assertEquals("myChangeId", commitFromExternalContributor.changeId)
+ assertEquals("New compat API!", commitFromExternalContributor.summary)
+ assertEquals(CommitType.EXTERNAL_CONTRIBUTION, commitFromExternalContributor.type)
+ assertEquals(mutableListOf(111111, 222222), commitFromExternalContributor.bugs)
+ assertEquals(
+ mutableListOf(
+ "$projectDir/a.java",
+ "$projectDir/b.java",
+ "$projectDir/androidTest/c.java",
+ "$projectDir/api/current.txt",
+ "$projectDir/api/1.2.0-alpha04.txt"
+ ),
+ commitFromExternalContributor.files
+ )
+ }
+
+ @Test
+ fun parseGitLog() {
+ /*
+ * This is the default set-up boilerplate from `GitClientImpl.getGitLog` to set up the
+ * default git log command (val gitLogCmd)
+ */
+ val commitStartDelimiter: String = "_CommitStart"
+ val commitSHADelimiter: String = "_CommitSHA:"
+ val subjectDelimiter: String = "_Subject:"
+ val authorEmailDelimiter: String = "_Author:"
+ val dateDelimiter: String = "_Date:"
+ val bodyDelimiter: String = "_Body:"
+ val projectDir: String = "group/artifact"
+ val gitLogOptions: String =
+ "--pretty=format:$commitStartDelimiter%n" +
+ "$commitSHADelimiter%H%n" +
+ "$authorEmailDelimiter%ae%n" +
+ "$dateDelimiter%ad%n" +
+ "$subjectDelimiter%s%n" +
+ "$bodyDelimiter%b" +
+ " --no-merges"
+ val gitLogCmd: String = "${GitClientImpl.GIT_LOG_CMD_PREFIX} " +
+ "$gitLogOptions sha..topSha $projectDir"
+
+ // Check with default delimiters
+ val gitLogString: String =
+ """
+ _CommitStart
+ _CommitSHA:topSha
+ _Author:anemail@google.com
+ _Date:Tue Aug 6 15:05:55 2019 -0700
+ _Subject:Added a new API!
+ _Body:Also fixed some other bugs
+
+ Here is an explanation of my commit
+
+ Bug: 123456
+ Bug: b/1234567
+ Fixes: 123123
+ Test: ./gradlew buildOnServer
+
+ Change-Id: myChangeId
+
+ $projectDir/a.java
+ $projectDir/b.java
+ $projectDir/androidTest/c.java
+ $projectDir/api/some_api_file.txt
+ $projectDir/api/current.txt
+
+ _CommitStart
+ _CommitSHA:midSha
+ _Author:anemail@google.com
+ _Date:Tue Aug 6 15:05:55 2019 -0700
+ _Subject:Fixed a bug!
+ _Body:Also fixed some other bugs
+
+ Here is an explanation of my commit
+
+ Bug: 111111, 222222
+ Test: ./gradlew buildOnServer
+
+ Change-Id: myChangeId
+
+ $projectDir/a.java
+ $projectDir/b.java
+ $projectDir/androidTest/c.java
+
+ _CommitStart
+ _CommitSHA:sha
+ _Author:externalcontributor@gmail.com
+ _Date:Thurs Aug 8 15:05:55 2019 -0700
+ _Subject:New compat API!
+ _Body:Also fixed some other bugs
+
+ Here is an explanation of my commit
+
+ Bug: 111111, 222222
+ Test: ./gradlew buildOnServer
+
+ Change-Id: myChangeId
+
+ $projectDir/a.java
+ $projectDir/b.java
+ $projectDir/androidTest/c.java
+ $projectDir/api/current.txt
+ $projectDir/api/1.2.0-alpha04.txt
+ """
+
+ commandRunner.addReply(
+ gitLogCmd,
+ gitLogString
+ )
+
+ val commitWithAPIChange: Commit = Commit("", projectDir)
+ commitWithAPIChange.sha = "topSha"
+ commitWithAPIChange.authorEmail = "anemail@google.com"
+ commitWithAPIChange.changeId = "myChangeId"
+ commitWithAPIChange.summary = "Added a new API!"
+ commitWithAPIChange.type = CommitType.API_CHANGE
+ commitWithAPIChange.bugs = mutableListOf(123456, 1234567, 123123)
+ commitWithAPIChange.files = mutableListOf(
+ "$projectDir/a.java",
+ "$projectDir/b.java",
+ "$projectDir/androidTest/c.java",
+ "$projectDir/api/some_api_file.txt",
+ "$projectDir/api/current.txt"
+ )
+
+ val commitWithBugFix: Commit = Commit("", projectDir)
+ commitWithBugFix.sha = "midSha"
+ commitWithBugFix.authorEmail = "anemail@google.com"
+ commitWithBugFix.changeId = "myChangeId"
+ commitWithBugFix.summary = "Fixed a bug!"
+ commitWithBugFix.type = CommitType.BUG_FIX
+ commitWithBugFix.bugs = mutableListOf(111111, 222222)
+ commitWithBugFix.files = mutableListOf(
+ "$projectDir/a.java",
+ "$projectDir/b.java",
+ "$projectDir/androidTest/c.java"
+ )
+
+ val commitFromExternalContributor: Commit = Commit("", projectDir)
+ commitFromExternalContributor.sha = "sha"
+ commitFromExternalContributor.authorEmail = "externalcontributor@gmail.com"
+ commitFromExternalContributor.changeId = "myChangeId"
+ commitFromExternalContributor.summary = "New compat API!"
+ commitFromExternalContributor.type = CommitType.EXTERNAL_CONTRIBUTION
+ commitFromExternalContributor.bugs = mutableListOf(111111, 222222)
+ commitFromExternalContributor.files = mutableListOf(
+ "$projectDir/a.java",
+ "$projectDir/b.java",
+ "$projectDir/androidTest/c.java",
+ "$projectDir/api/current.txt",
+ "$projectDir/api/1.2.0-alpha04.txt"
+ )
+
+ val gitLogList: List<Commit> = client.getGitLog(
+ top = "topSha",
+ sha = "sha",
+ keepMerges = false,
+ fullProjectDir = File(projectDir)
+ )
+ gitLogList.forEach { commit ->
+ when (commit.sha) {
+ "topSha" -> assertCommitsAreEqual(commitWithAPIChange, commit)
+ "midSha" -> assertCommitsAreEqual(commitWithBugFix, commit)
+ "sha" -> assertCommitsAreEqual(commitFromExternalContributor, commit)
+ else -> throw RuntimeException("Incorrectly parsed commit: $commit")
+ }
+ }
+ }
+
+ fun assertCommitsAreEqual(commitA: Commit, commitB: Commit) {
+ assertEquals(commitA.summary, commitB.summary)
+ assertEquals(commitA.files, commitB.files)
+ assertEquals(commitA.sha, commitB.sha)
+ assertEquals(commitA.changeId, commitB.changeId)
+ assertEquals(commitA.authorEmail, commitB.authorEmail)
+ assertEquals(commitA.type, commitB.type)
+ assertEquals(commitA.projectDir, commitB.projectDir)
+ assertEquals(commitA.summary, commitB.summary)
+ }
+
// For both Linux/Windows
fun convertToFilePath(vararg list: String): String {
return list.toList().joinToString(File.separator)
@@ -105,7 +424,14 @@
replies[command] = response.split(System.lineSeparator())
}
- override fun execute(command: String): List<String> {
+ override fun execute(command: String): String {
+ return replies.getOrDefault(command, emptyList())
+ .joinToString(System.lineSeparator()).also {
+ logger.info("cmd: $command response: $it")
+ }
+ }
+
+ override fun executeAndParse(command: String): List<String> {
return replies.getOrDefault(command, emptyList()).also {
logger.info("cmd: $command response: $it")
}
diff --git a/buildSrc/src/main/kotlin/androidx/build/AndroidXPlugin.kt b/buildSrc/src/main/kotlin/androidx/build/AndroidXPlugin.kt
index b79b734..a69919f 100644
--- a/buildSrc/src/main/kotlin/androidx/build/AndroidXPlugin.kt
+++ b/buildSrc/src/main/kotlin/androidx/build/AndroidXPlugin.kt
@@ -41,6 +41,7 @@
import androidx.build.metalava.MetalavaTasks.configureAndroidProjectForMetalava
import androidx.build.metalava.MetalavaTasks.configureJavaProjectForMetalava
import androidx.build.metalava.UpdateApiTask
+import androidx.build.releasenotes.GenerateReleaseNotesTask
import com.android.build.gradle.AppExtension
import com.android.build.gradle.AppPlugin
import com.android.build.gradle.LibraryExtension
@@ -73,6 +74,8 @@
import org.jetbrains.kotlin.gradle.plugin.KotlinMultiplatformPluginWrapper
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
import java.io.File
+import java.time.LocalDate
+import java.time.format.DateTimeFormatter
import java.util.concurrent.ConcurrentHashMap
/**
@@ -130,6 +133,7 @@
if (verifyDependencyVersionsTask != null) {
project.createCheckReleaseReadyTask(listOf(verifyDependencyVersionsTask))
}
+ project.createGenerateReleaseNotesTask()
project.configureNonAndroidProjectForLint(androidXExtension)
project.configureJavaProjectForDokka(androidXExtension)
project.configureJavaProjectForMetalava(androidXExtension)
@@ -157,6 +161,7 @@
if (checkReleaseReadyTasks.isNotEmpty()) {
project.createCheckReleaseReadyTask(checkReleaseReadyTasks)
}
+ project.createGenerateReleaseNotesTask()
val reportLibraryMetrics = project.tasks.register(
REPORT_LIBRARY_METRICS, ReportLibraryMetricsTask::class.java
)
@@ -605,6 +610,37 @@
VerifyDependencyVersionsTask::class.java)
}
+ // Task that creates release notes
+ private fun Project.createGenerateReleaseNotesTask() {
+ tasks.register(
+ GENERATE_RELEASE_NOTES_TASK,
+ GenerateReleaseNotesTask::class.java
+ ) { task ->
+ if (project.hasProperty("startCommit")) {
+ task.startSHA = project.property("startCommit").toString()
+ } else {
+ task.startSHA = ""
+ }
+ if (project.hasProperty("endCommit")) {
+ task.endSHA = project.property("endCommit").toString()
+ } else {
+ task.endSHA = "HEAD"
+ }
+ val formatter = DateTimeFormatter.ofPattern("MM-d-yyyy")
+ if (project.hasProperty("releaseDate")) {
+ task.date = LocalDate.parse(project.property("releaseDate").toString(), formatter)
+ } else {
+ task.date = LocalDate.now()
+ }
+ task.outputFile.set(
+ File(
+ project.getReleaseNotesDirectory(),
+ "${group}_${name}_release_notes.txt"
+ )
+ )
+ }
+ }
+
// Task that creates a json file of a project's dependencies
private fun Project.addCreateLibraryBuildInfoFileTask(extension: AndroidXExtension) {
afterEvaluate {
@@ -663,6 +699,7 @@
const val CHECK_SAME_VERSION_LIBRARY_GROUPS = "checkSameVersionLibraryGroups"
const val CREATE_LIBRARY_BUILD_INFO_FILES_TASK = "createLibraryBuildInfoFiles"
const val CREATE_AGGREGATE_BUILD_INFO_FILES_TASK = "createAggregateBuildInfoFiles"
+ const val GENERATE_RELEASE_NOTES_TASK = "generateReleaseNotes"
const val REPORT_LIBRARY_METRICS = "reportLibraryMetrics"
}
}
diff --git a/buildSrc/src/main/kotlin/androidx/build/BuildServerConfiguration.kt b/buildSrc/src/main/kotlin/androidx/build/BuildServerConfiguration.kt
index aa1f726..e273562 100644
--- a/buildSrc/src/main/kotlin/androidx/build/BuildServerConfiguration.kt
+++ b/buildSrc/src/main/kotlin/androidx/build/BuildServerConfiguration.kt
@@ -59,6 +59,12 @@
File(getDistributionDirectory(), "build-info")
/**
+ * Directory to put release note files for generate release note tasks.
+ */
+fun Project.getReleaseNotesDirectory(): File =
+ File(getDistributionDirectory(), "release-notes")
+
+/**
* Directory to put host test results so they can be consumed by the testing dashboard.
*/
fun Project.getHostTestResultDirectory(): File =
diff --git a/buildSrc/src/main/kotlin/androidx/build/LibraryVersions.kt b/buildSrc/src/main/kotlin/androidx/build/LibraryVersions.kt
index 0800b84..612ef2c 100644
--- a/buildSrc/src/main/kotlin/androidx/build/LibraryVersions.kt
+++ b/buildSrc/src/main/kotlin/androidx/build/LibraryVersions.kt
@@ -32,8 +32,8 @@
val ARCH_RUNTIME = Version("2.2.0-alpha01")
val ASYNCLAYOUTINFLATER = Version("1.1.0-alpha01")
val AUTOFILL = Version("1.0.0-alpha03")
- val BENCHMARK = Version("1.0.0-alpha04")
- val BIOMETRIC = Version("1.0.0-beta01")
+ val BENCHMARK = Version("1.0.0-alpha05")
+ val BIOMETRIC = Version("1.0.0-alpha06")
val BROWSER = Version("1.2.0-alpha08")
val CAMERA = Version("1.0.0-alpha05")
val CAMERA_EXTENSIONS = Version("1.0.0-alpha02")
@@ -55,7 +55,7 @@
val DYNAMICANIMATION = Version("1.1.0-alpha03")
val DYNAMICANIMATION_KTX = Version("1.0.0-alpha03")
val EMOJI = Version("1.1.0-alpha01")
- val ENTERPRISE = Version("1.0.0-alpha04")
+ val ENTERPRISE = Version("1.0.0-beta01")
val EXIFINTERFACE = Version("1.1.0-beta02")
val FRAGMENT = Version("1.2.0-alpha03")
val FUTURES = Version("1.0.0-rc01")
@@ -77,7 +77,7 @@
val MEDIA2_PLAYER = Version("1.1.0-alpha01")
val MEDIA2_SESSION = Version("1.1.0-alpha01")
val MEDIA2_EXOPLAYER = Version("1.0.0-rc01")
- val MEDIA2_WIDGET = Version("1.0.0-beta02")
+ val MEDIA2_WIDGET = Version("1.0.0-rc02")
val MEDIAROUTER = Version("1.2.0-alpha01")
val NAVIGATION = Version("2.2.0-alpha02")
val NAVIGATION_TESTING = Version("1.0.0-alpha08") // Unpublished
@@ -87,14 +87,14 @@
val PERCENTLAYOUT = Version("1.1.0-alpha01")
val PREFERENCE = Version("1.2.0-alpha01")
val RECOMMENDATION = Version("1.1.0-alpha01")
- val RECYCLERVIEW = Version("1.1.0-beta03")
+ val RECYCLERVIEW = Version("1.1.0-beta04")
val RECYCLERVIEW_SELECTION = Version("1.1.0-alpha07")
val REMOTECALLBACK = Version("1.0.0-alpha02")
- val ROOM = Version("2.2.0-alpha03")
+ val ROOM = Version("2.2.0-beta01")
val SAVEDSTATE = Version("1.0.0-rc01")
val SECURITY = Version("1.0.0-alpha03")
val SECURITY_IDENTITY_CREDENTIAL = Version("1.0.0-alpha01")
- val SHARETARGET = Version("1.0.0-alpha03")
+ val SHARETARGET = Version("1.0.0-beta01")
val SLICE = Version("1.1.0-alpha02")
val SLICE_BENCHMARK = Version("1.1.0-alpha02")
val SLICE_BUILDERS_KTX = Version("1.0.0-alpha08")
@@ -103,7 +103,7 @@
val SWIPE_REFRESH_LAYOUT = Version("1.1.0-alpha03")
val TEST_SCREENSHOT = Version("1.0.0-alpha01")
val TEXTCLASSIFIER = Version("1.0.0-alpha03")
- val TRANSITION = Version("1.2.0-beta02")
+ val TRANSITION = Version("1.2.0-rc01")
val TVPROVIDER = Version("1.1.0-alpha01")
val UI = Version("1.0.0-alpha01")
val VECTORDRAWABLE = Version("1.2.0-alpha01")
diff --git a/buildSrc/src/main/kotlin/androidx/build/PublishDocsRules.kt b/buildSrc/src/main/kotlin/androidx/build/PublishDocsRules.kt
index fd3836f..9c03f27 100644
--- a/buildSrc/src/main/kotlin/androidx/build/PublishDocsRules.kt
+++ b/buildSrc/src/main/kotlin/androidx/build/PublishDocsRules.kt
@@ -31,13 +31,13 @@
prebuilts(LibraryGroups.ACTIVITY, "1.1.0-alpha02")
prebuilts(LibraryGroups.ADS, "1.0.0-alpha01")
prebuilts(LibraryGroups.ANNOTATION, "annotation", "1.1.0")
- prebuilts(LibraryGroups.APPCOMPAT, "1.1.0-rc01")
- prebuilts(LibraryGroups.ARCH_CORE, "2.1.0-rc01")
+ prebuilts(LibraryGroups.APPCOMPAT, "1.1.0")
+ prebuilts(LibraryGroups.ARCH_CORE, "2.1.0")
prebuilts(LibraryGroups.ASYNCLAYOUTINFLATER, "1.0.0")
prebuilts(LibraryGroups.AUTOFILL, "1.0.0-alpha02")
ignore(LibraryGroups.BENCHMARK.group, "benchmark-gradle-plugin")
prebuilts(LibraryGroups.BENCHMARK, "1.0.0-alpha04")
- prebuilts(LibraryGroups.BIOMETRIC, "biometric", "1.0.0-beta01")
+ prebuilts(LibraryGroups.BIOMETRIC, "biometric", "1.0.0-alpha04")
prebuilts(LibraryGroups.BROWSER, "1.2.0-alpha07")
ignore(LibraryGroups.CAMERA.group, "camera-testing")
ignore(LibraryGroups.CAMERA.group, "camera-extensions-stub")
@@ -60,7 +60,7 @@
prebuilts(LibraryGroups.CURSORADAPTER, "1.0.0")
prebuilts(LibraryGroups.CUSTOMVIEW, "1.1.0-alpha01")
prebuilts(LibraryGroups.DOCUMENTFILE, "1.0.0")
- prebuilts(LibraryGroups.DRAWERLAYOUT, "1.1.0-alpha02")
+ prebuilts(LibraryGroups.DRAWERLAYOUT, "1.1.0-alpha03")
prebuilts(LibraryGroups.DYNAMICANIMATION, "dynamicanimation-ktx", "1.0.0-alpha02")
prebuilts(LibraryGroups.DYNAMICANIMATION, "1.1.0-alpha02")
prebuilts(LibraryGroups.EMOJI, "1.0.0")
@@ -79,12 +79,13 @@
ignore(LibraryGroups.LOADER.group, "loader-ktx")
prebuilts(LibraryGroups.LOADER, "1.1.0-rc01")
prebuilts(LibraryGroups.LOCALBROADCASTMANAGER, "1.1.0-alpha01")
- prebuilts(LibraryGroups.MEDIA, "media", "1.1.0-rc01")
+ prebuilts(LibraryGroups.MEDIA, "media", "1.1.0")
ignore(LibraryGroups.MEDIA2.group, "media2-exoplayer")
- prebuilts(LibraryGroups.MEDIA2, "media2-widget", "1.0.0-beta01")
- prebuilts(LibraryGroups.MEDIA2, "1.0.0-rc01")
+ prebuilts(LibraryGroups.MEDIA2, "media2-widget", "1.0.0-rc02")
+ prebuilts(LibraryGroups.MEDIA2, "1.0.0-rc02")
prebuilts(LibraryGroups.MEDIAROUTER, "1.1.0-rc01")
ignore(LibraryGroups.NAVIGATION.group, "navigation-testing")
+ ignore(LibraryGroups.NAVIGATION.group, "navigation-dynamic-feature")
ignore(LibraryGroups.NAVIGATION.group, "navigation-safe-args-generator")
ignore(LibraryGroups.NAVIGATION.group, "navigation-safe-args-gradle-plugin")
prebuilts(LibraryGroups.NAVIGATION, "2.2.0-alpha01")
@@ -95,11 +96,11 @@
prebuilts(LibraryGroups.PREFERENCE, "1.1.0-rc01")
prebuilts(LibraryGroups.PRINT, "1.0.0")
prebuilts(LibraryGroups.RECOMMENDATION, "1.0.0")
- prebuilts(LibraryGroups.RECYCLERVIEW, "recyclerview", "1.1.0-beta02")
+ prebuilts(LibraryGroups.RECYCLERVIEW, "recyclerview", "1.1.0-beta03")
prebuilts(LibraryGroups.RECYCLERVIEW, "recyclerview-selection", "1.1.0-alpha06")
prebuilts(LibraryGroups.REMOTECALLBACK, "1.0.0-alpha02")
- prebuilts(LibraryGroups.ROOM, "2.2.0-alpha02")
- prebuilts(LibraryGroups.SAVEDSTATE, "1.0.0-beta01")
+ prebuilts(LibraryGroups.ROOM, "2.2.0-beta01")
+ prebuilts(LibraryGroups.SAVEDSTATE, "1.0.0")
// TODO: Remove this ignore once androidx.security:security-identity-credential:1.0.0-alpha01 is released
ignore(LibraryGroups.SECURITY.group, "security-identity-credential")
prebuilts(LibraryGroups.SECURITY, "1.0.0-alpha02")
@@ -117,8 +118,8 @@
prebuilts(LibraryGroups.TEXTCLASSIFIER, "1.0.0-alpha02")
prebuilts(LibraryGroups.TRANSITION, "1.2.0-beta01")
prebuilts(LibraryGroups.TVPROVIDER, "1.0.0")
- prebuilts(LibraryGroups.VECTORDRAWABLE, "1.1.0-rc01")
- prebuilts(LibraryGroups.VECTORDRAWABLE, "vectordrawable-animated", "1.1.0-rc01")
+ prebuilts(LibraryGroups.VECTORDRAWABLE, "1.1.0")
+ prebuilts(LibraryGroups.VECTORDRAWABLE, "vectordrawable-animated", "1.1.0")
prebuilts(LibraryGroups.VERSIONEDPARCELABLE, "1.1.0")
prebuilts(LibraryGroups.VIEWPAGER, "1.0.0")
prebuilts(LibraryGroups.VIEWPAGER2, "1.0.0-beta03")
@@ -127,7 +128,7 @@
prebuilts(LibraryGroups.WEBKIT, "1.1.0-alpha02")
ignore(LibraryGroups.WORK.group, "work-gcm")
ignore(LibraryGroups.WORK.group, "work-foreground")
- prebuilts(LibraryGroups.WORK, "2.2.0-rc01")
+ prebuilts(LibraryGroups.WORK, "2.3.0-alpha01")
default(Ignore)
}
diff --git a/buildSrc/src/main/kotlin/androidx/build/dependencyTracker/AffectedModuleDetector.kt b/buildSrc/src/main/kotlin/androidx/build/dependencyTracker/AffectedModuleDetector.kt
index d3df2ff..a93b352 100644
--- a/buildSrc/src/main/kotlin/androidx/build/dependencyTracker/AffectedModuleDetector.kt
+++ b/buildSrc/src/main/kotlin/androidx/build/dependencyTracker/AffectedModuleDetector.kt
@@ -20,6 +20,8 @@
import androidx.build.dependencyTracker.AffectedModuleDetector.Companion.DEPENDENT_PROJECTS_ARG
import androidx.build.dependencyTracker.AffectedModuleDetector.Companion.ENABLE_ARG
import androidx.build.getDistributionDirectory
+import androidx.build.gitclient.GitClient
+import androidx.build.gitclient.GitClientImpl
import androidx.build.gradle.isRoot
import androidx.build.isRunningOnBuildServer
import java.io.File
diff --git a/buildSrc/src/main/kotlin/androidx/build/dependencyTracker/GitClient.kt b/buildSrc/src/main/kotlin/androidx/build/dependencyTracker/GitClient.kt
deleted file mode 100644
index 486df04..0000000
--- a/buildSrc/src/main/kotlin/androidx/build/dependencyTracker/GitClient.kt
+++ /dev/null
@@ -1,115 +0,0 @@
-/*
- * Copyright 2018 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.build.dependencyTracker
-
-import org.gradle.api.logging.Logger
-import java.io.File
-import java.util.concurrent.TimeUnit
-
-interface GitClient {
- fun findChangedFilesSince(
- sha: String,
- top: String = "HEAD",
- includeUncommitted: Boolean = false
- ): List<String>
- fun findPreviousMergeCL(): String?
-
- /**
- * Abstraction for running execution commands for testability
- */
- interface CommandRunner {
- /**
- * Executes the given shell command and returns the stdout by lines.
- */
- fun execute(command: String): List<String>
- }
-}
-/**
- * A simple git client that uses system process commands to communicate with the git setup in the
- * given working directory.
- */
-class GitClientImpl(
- /**
- * The root location for git
- */
- private val workingDir: File,
- private val logger: Logger? = null,
- private val commandRunner: GitClient.CommandRunner = RealCommandRunner(
- workingDir = workingDir,
- logger = logger
- )
-) : GitClient {
- /**
- * Finds changed file paths since the given sha
- */
- override fun findChangedFilesSince(
- sha: String,
- top: String,
- includeUncommitted: Boolean
- ): List<String> {
- // use this if we don't want local changes
- return if (includeUncommitted) {
- "$CHANGED_FILES_CMD_PREFIX $sha"
- } else {
- "$CHANGED_FILES_CMD_PREFIX $top $sha"
- }.runCommand()
- }
-
- /**
- * checks the history to find the first merge CL.
- */
- override fun findPreviousMergeCL(): String? {
- return PREV_MERGE_CMD
- .runCommand()
- .firstOrNull()
- ?.split(" ")
- ?.firstOrNull()
- }
-
- private fun String.runCommand() = commandRunner.execute(this)
-
- private class RealCommandRunner(
- private val workingDir: File,
- private val logger: Logger?
- ) : GitClient.CommandRunner {
- override fun execute(command: String): List<String> {
- val parts = command.split("\\s".toRegex())
- logger?.info("running command $command")
- val proc = ProcessBuilder(*parts.toTypedArray())
- .directory(workingDir)
- .redirectOutput(ProcessBuilder.Redirect.PIPE)
- .redirectError(ProcessBuilder.Redirect.PIPE)
- .start()
-
- proc.waitFor(1, TimeUnit.MINUTES)
- val response = proc
- .inputStream
- .bufferedReader()
- .readLines()
- .filterNot {
- it.isEmpty()
- }
- logger?.info("Response: ${response.joinToString(System.lineSeparator())}")
- return response
- }
- }
-
- companion object {
- const val PREV_MERGE_CMD = "git log -1 --merges --oneline"
- const val CHANGED_FILES_CMD_PREFIX = "git diff --name-only"
- }
-}
diff --git a/buildSrc/src/main/kotlin/androidx/build/gitclient/GitClient.kt b/buildSrc/src/main/kotlin/androidx/build/gitclient/GitClient.kt
new file mode 100644
index 0000000..7419cc0
--- /dev/null
+++ b/buildSrc/src/main/kotlin/androidx/build/gitclient/GitClient.kt
@@ -0,0 +1,347 @@
+/*
+ * Copyright 2018 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.build.gitclient
+
+import androidx.build.releasenotes.getAOSPLink
+import androidx.build.releasenotes.getBuganizerLink
+import org.gradle.api.logging.Logger
+import java.io.File
+import java.util.concurrent.TimeUnit
+
+interface GitClient {
+ fun findChangedFilesSince(
+ sha: String,
+ top: String = "HEAD",
+ includeUncommitted: Boolean = false
+ ): List<String>
+ fun findPreviousMergeCL(): String?
+
+ fun getGitLog(
+ sha: String,
+ top: String = "HEAD",
+ keepMerges: Boolean,
+ fullProjectDir: File
+ ): List<Commit>
+
+ /**
+ * Abstraction for running execution commands for testability
+ */
+ interface CommandRunner {
+ /**
+ * Executes the given shell command and returns the stdout as a string.
+ */
+ fun execute(command: String): String
+ /**
+ * Executes the given shell command and returns the stdout by lines.
+ */
+ fun executeAndParse(command: String): List<String>
+ }
+}
+/**
+ * A simple git client that uses system process commands to communicate with the git setup in the
+ * given working directory.
+ */
+class GitClientImpl(
+ /**
+ * The root location for git
+ */
+ private val workingDir: File,
+ private val logger: Logger? = null,
+ private val commandRunner: GitClient.CommandRunner = RealCommandRunner(
+ workingDir = workingDir,
+ logger = logger
+ )
+) : GitClient {
+
+ private val gitRoot: File = findGitDirInParentFilepath(workingDir) ?: workingDir
+
+ /**
+ * Finds changed file paths since the given sha
+ */
+ override fun findChangedFilesSince(
+ sha: String,
+ top: String,
+ includeUncommitted: Boolean
+ ): List<String> {
+ // use this if we don't want local changes
+ return commandRunner.executeAndParse(if (includeUncommitted) {
+ "$CHANGED_FILES_CMD_PREFIX $sha"
+ } else {
+ "$CHANGED_FILES_CMD_PREFIX $top $sha"
+ })
+ }
+
+ /**
+ * checks the history to find the first merge CL.
+ */
+ override fun findPreviousMergeCL(): String? {
+ return commandRunner.executeAndParse(PREV_MERGE_CMD)
+ .firstOrNull()
+ ?.split(" ")
+ ?.firstOrNull()
+ }
+
+ private fun findGitDirInParentFilepath(filepath: File): File? {
+ var curDirectory: File = filepath
+ while (curDirectory.path != "/") {
+ if (File("$curDirectory/.git").exists()) {
+ return curDirectory
+ }
+ curDirectory = curDirectory.parentFile
+ }
+ return null
+ }
+
+ /**
+ * Converts a diff log command into a [List<Commit>]
+ *
+ * @param sha the SHA at which the git log starts.
+ * @param top the last SHA included in the git log.
+ * @param keepMerges boolean for whether or not to add merges to the return [List<Commit>].
+ * @param fullProjectDir a [File] object that represents the full project directory.
+ */
+ override fun getGitLog(
+ sha: String,
+ top: String,
+ keepMerges: Boolean,
+ fullProjectDir: File
+ ): List<Commit> {
+ val commitStartDelimiter: String = "_CommitStart"
+ val commitSHADelimiter: String = "_CommitSHA:"
+ val subjectDelimiter: String = "_Subject:"
+ val authorEmailDelimiter: String = "_Author:"
+ val dateDelimiter: String = "_Date:"
+ val bodyDelimiter: String = "_Body:"
+ val localProjectDir: String = fullProjectDir.toString()
+ .removePrefix(gitRoot.toString())
+
+ var gitLogOptions: String =
+ "--pretty=format:$commitStartDelimiter%n" +
+ "$commitSHADelimiter%H%n" +
+ "$authorEmailDelimiter%ae%n" +
+ "$dateDelimiter%ad%n" +
+ "$subjectDelimiter%s%n" +
+ "$bodyDelimiter%b" +
+ if (!keepMerges) {
+ " --no-merges"
+ } else {
+ ""
+ }
+ val gitLogCmd: String = "$GIT_LOG_CMD_PREFIX $gitLogOptions $sha..$top $fullProjectDir"
+ val gitLogString: String = commandRunner.execute(gitLogCmd)
+
+ // Split commits string out into individual commits (note: this removes the deliminter)
+ val gitLogStringList: List<String>? = gitLogString.split(commitStartDelimiter)
+ var commitLog: MutableList<Commit> = mutableListOf()
+ gitLogStringList?.filter { gitCommit ->
+ gitCommit.trim() != ""
+ }?.forEach { gitCommit ->
+ commitLog.add(
+ Commit(
+ gitCommit,
+ localProjectDir,
+ commitSHADelimiter = commitSHADelimiter,
+ subjectDelimiter = subjectDelimiter,
+ authorEmailDelimiter = authorEmailDelimiter
+ ))
+ }
+ return commitLog.toList()
+ }
+
+ private class RealCommandRunner(
+ private val workingDir: File,
+ private val logger: Logger?
+ ) : GitClient.CommandRunner {
+ override fun execute(command: String): String {
+ val parts = command.split("\\s".toRegex())
+ logger?.info("running command $command")
+ val proc = ProcessBuilder(*parts.toTypedArray())
+ .directory(workingDir)
+ .redirectOutput(ProcessBuilder.Redirect.PIPE)
+ .redirectError(ProcessBuilder.Redirect.PIPE)
+ .start()
+
+ proc.waitFor(1, TimeUnit.MINUTES)
+ val response = proc
+ .inputStream
+ .bufferedReader()
+ .readText()
+ logger?.info("Response: $response")
+ return response
+ }
+ override fun executeAndParse(command: String): List<String> {
+ val response = execute(command)
+ .split(System.lineSeparator())
+ .filterNot {
+ it.isEmpty()
+ }
+ return response
+ }
+ }
+
+ companion object {
+ const val PREV_MERGE_CMD = "git log -1 --merges --oneline"
+ const val CHANGED_FILES_CMD_PREFIX = "git diff --name-only"
+ const val GIT_LOG_CMD_PREFIX = "git log --name-only"
+ }
+}
+
+enum class CommitType {
+ NEW_FEATURE, API_CHANGE, BUG_FIX, EXTERNAL_CONTRIBUTION;
+ companion object {
+ fun getTitle(commitType: CommitType): String {
+ return when (commitType) {
+ NEW_FEATURE -> "New Features"
+ API_CHANGE -> "API Changes"
+ BUG_FIX -> "Bug Fixes"
+ EXTERNAL_CONTRIBUTION -> "External Contribution"
+ }
+ }
+ }
+}
+
+/**
+ * Class implementation of a git commit. It uses the input delimiters to parse the commit
+ *
+ * @property gitCommit a string representation of a git commit
+ * @property projectDir the project directory for which to parse file paths from a commit
+ * @property commitSHADelimiter the term to use to search for the commit SHA
+ * @property subjectDelimiter the term to use to search for the subject (aka commit summary)
+ * @property changeIdDelimiter the term to use to search for the change-id in the body of the commit
+ * message
+ * @property authorEmailDelimiter the term to use to search for the author email
+ */
+data class Commit(
+ val gitCommit: String,
+ val projectDir: String,
+ private val commitSHADelimiter: String = "_CommitSHA:",
+ private val subjectDelimiter: String = "_Subject:",
+ private val authorEmailDelimiter: String = "_Author:"
+) {
+ private val changeIdDelimiter: String = "Change-Id:"
+ var bugs: MutableList<Int> = mutableListOf()
+ var files: MutableList<String> = mutableListOf()
+ var sha: String = ""
+ var authorEmail: String = ""
+ var changeId: String = ""
+ var summary: String = ""
+ var type: CommitType = CommitType.BUG_FIX
+
+ init {
+ val listedCommit: List<String> = gitCommit.split('\n')
+ listedCommit.filter { line -> line.trim() != "" }.forEach { line ->
+ if (commitSHADelimiter in line) {
+ getSHAFromGitLine(line)
+ }
+ if (subjectDelimiter in line) {
+ getSummary(line)
+ }
+ if (changeIdDelimiter in line) {
+ getChangeIdFromGitLine(line)
+ }
+ if (authorEmailDelimiter in line) {
+ getAuthorEmailFromGitLine(line)
+ }
+ if ("Bug:" in line ||
+ "b/" in line ||
+ "bug:" in line ||
+ "Fixes:" in line ||
+ "fixes b/" in line
+ ) {
+ getBugsFromGitLine(line)
+ }
+ if (projectDir.trim('/') in line) {
+ getFileFromGitLine(line)
+ }
+ }
+ }
+
+ private fun isExternalAuthorEmail(authorEmail: String): Boolean {
+ return !(authorEmail.contains("@google.com"))
+ }
+
+ /**
+ * Parses SHAs from git commit line, with the format:
+ * [Commit.commitSHADelimiter] <commitSHA>
+ */
+ private fun getSHAFromGitLine(line: String) {
+ sha = line.substringAfter(commitSHADelimiter).trim()
+ }
+
+ /**
+ * Parses subject from git commit line, with the format:
+ * [Commit.subjectDelimiter]<commit subject>
+ */
+ private fun getSummary(line: String) {
+ summary = line.substringAfter(subjectDelimiter).trim()
+ }
+
+ /**
+ * Parses commit Change-Id lines, with the format:
+ * `commit.changeIdDelimiter` <changeId>
+ */
+ private fun getChangeIdFromGitLine(line: String) {
+ changeId = line.substringAfter(changeIdDelimiter).trim()
+ }
+
+ /**
+ * Parses commit author lines, with the format:
+ * [Commit.authorEmailDelimiter]email@google.com
+ */
+ private fun getAuthorEmailFromGitLine(line: String) {
+ authorEmail = line.substringAfter(authorEmailDelimiter).trim()
+ if (isExternalAuthorEmail(authorEmail)) {
+ type = CommitType.EXTERNAL_CONTRIBUTION
+ }
+ }
+
+ /**
+ * Parses filepath to get changed files from commit, with the format:
+ * {project_directory}/{filepath}
+ */
+ private fun getFileFromGitLine(filepath: String) {
+ files.add(filepath.trim())
+ if (filepath.contains("current.txt") && type != CommitType.EXTERNAL_CONTRIBUTION) {
+ type = CommitType.API_CHANGE
+ }
+ }
+
+ /**
+ * Parses bugs from a git commit message line
+ */
+ private fun getBugsFromGitLine(line: String) {
+ var formattedLine = line.replace("b/", " ")
+ formattedLine = formattedLine.replace(":", " ")
+ formattedLine = formattedLine.replace(",", " ")
+ var words: List<String> = formattedLine.split(' ')
+ words.forEach { word ->
+ var possibleBug: Int? = word.toIntOrNull()
+ if (possibleBug != null && possibleBug > 1000) {
+ bugs.add(possibleBug)
+ }
+ }
+ }
+
+ override fun toString(): String {
+ var commitString: String = summary
+ commitString += " ${getAOSPLink(changeId)}"
+ bugs.forEach { bug ->
+ commitString += " ${getBuganizerLink(bug)}"
+ }
+ return commitString
+ }
+}
diff --git a/buildSrc/src/main/kotlin/androidx/build/metalava/MetalavaRunner.kt b/buildSrc/src/main/kotlin/androidx/build/metalava/MetalavaRunner.kt
index 1067c50..edc6fa0 100644
--- a/buildSrc/src/main/kotlin/androidx/build/metalava/MetalavaRunner.kt
+++ b/buildSrc/src/main/kotlin/androidx/build/metalava/MetalavaRunner.kt
@@ -186,7 +186,9 @@
// Never track @Experimental APIs.
args += listOf(
"--hide-annotation", "androidx.annotation.experimental.Experimental",
- "--hide-meta-annotation", "androidx.annotation.experimental.Experimental"
+ "--hide-annotation", "kotlin.Experimental",
+ "--hide-meta-annotation", "androidx.annotation.experimental.Experimental",
+ "--hide-meta-annotation", "kotlin.Experimental"
)
val metalavaConfiguration = getMetalavaConfiguration()
diff --git a/buildSrc/src/main/kotlin/androidx/build/metalava/MetalavaTask.kt b/buildSrc/src/main/kotlin/androidx/build/metalava/MetalavaTask.kt
index a68fe6f..78480c81 100644
--- a/buildSrc/src/main/kotlin/androidx/build/metalava/MetalavaTask.kt
+++ b/buildSrc/src/main/kotlin/androidx/build/metalava/MetalavaTask.kt
@@ -27,6 +27,7 @@
abstract class MetalavaTask : DefaultTask() {
/** Configuration containing Metalava and its dependencies. */
@get:Classpath
+ @get:InputFiles
lateinit var configuration: Configuration
/** Android's boot classpath. Obtained from [BaseExtension.getBootClasspath]. */
diff --git a/buildSrc/src/main/kotlin/androidx/build/metalava/MetalavaTasks.kt b/buildSrc/src/main/kotlin/androidx/build/metalava/MetalavaTasks.kt
index 76f5d55..022cb45 100644
--- a/buildSrc/src/main/kotlin/androidx/build/metalava/MetalavaTasks.kt
+++ b/buildSrc/src/main/kotlin/androidx/build/metalava/MetalavaTasks.kt
@@ -183,6 +183,20 @@
}
}
+ val regenerateOldApis = project.tasks.register("regenerateOldApis",
+ RegenerateOldApisTask::class.java) { task ->
+ task.group = "API"
+ task.description = "Regenerates historic API .txt files using the " +
+ "corresponding prebuilt and the latest Metalava"
+ // if checkApiRelease and regenerateOldApis both run, then checkApiRelease must
+ // be the second one run of the two (because checkApiRelease validates
+ // files modified by regenerateOldApis)
+ val cr = checkApiRelease
+ if (cr != null) {
+ cr.get().mustRunAfter(task)
+ }
+ }
+
val updateApi = project.tasks.register("updateApi", UpdateApiTask::class.java) { task ->
task.group = "API"
task.description = "Updates the checked in API files to match source code API"
@@ -200,13 +214,11 @@
}
}
- project.tasks.register("regenerateOldApis", RegenerateOldApisTask::class.java) { task ->
+ project.tasks.register("regenerateApis") { task ->
task.group = "API"
task.description = "Regenerates current and historic API .txt files using the " +
"corresponding prebuilt and the latest Metalava"
- // Technically this doesn't need updateApi to happen first, but adding this dependency
- // is a convenient way to make updateApi also happen when the user runs
- // `./gradlew regenerateOldApis`
+ task.dependsOn(regenerateOldApis)
task.dependsOn(updateApi)
}
diff --git a/buildSrc/src/main/kotlin/androidx/build/releasenotes/GenerateReleaseNotesTask.kt b/buildSrc/src/main/kotlin/androidx/build/releasenotes/GenerateReleaseNotesTask.kt
new file mode 100644
index 0000000..8380060
--- /dev/null
+++ b/buildSrc/src/main/kotlin/androidx/build/releasenotes/GenerateReleaseNotesTask.kt
@@ -0,0 +1,138 @@
+/*
+ * Copyright (C) 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.build.releasenotes
+
+import androidx.build.gitclient.Commit
+import androidx.build.gitclient.GitClientImpl
+import androidx.build.getReleaseNotesDirectory
+import org.gradle.api.DefaultTask
+import org.gradle.api.provider.Property
+import org.gradle.api.tasks.Input
+import org.gradle.api.tasks.OutputFile
+import org.gradle.api.tasks.TaskAction
+import java.io.File
+import java.time.LocalDate
+
+/**
+ * Task for Generating Release notes for this specific project, based on a start SHA and an end SHA.
+ * It outputs release notes in the format of developer.android.com. See [LibraryReleaseNotes] for
+ * more info about the formatting.
+ */
+open class GenerateReleaseNotesTask : DefaultTask() {
+
+ init {
+ group = "Documentation"
+ description = "Task for creating release notes for a specific library"
+ }
+
+ @Input
+ lateinit var startSHA: String
+ @Input
+ lateinit var endSHA: String
+ @Input
+ lateinit var date: LocalDate
+ @Input
+ var keepMerges: Boolean = false
+
+ @OutputFile
+ val outputFile: Property<File> = project.objects.property(File::class.java)
+
+ /**
+ * @return the local project directory without the full framework/support root directory path
+ * Example input: /Users/<username>/androidx-master-dev/frameworks/support/core/core
+ * Example output: /core/core
+ */
+ private fun getProjectSpecificDirectory(): String {
+ return project.projectDir.toString().removePrefix(project.rootDir.toString())
+ }
+
+ /**
+ * Check if an email address is a robot
+ *
+ * @param authorEmail email to check
+ */
+ fun isExcludedAuthorEmail(authorEmail: String): Boolean {
+ val excludedAuthorEmails = listOf(
+ "treehugger-gerrit@google.com",
+ "android-build-merger@google.com",
+ "noreply-gerritcodereview@google.com"
+ )
+ return excludedAuthorEmails.contains(authorEmail)
+ }
+
+ private fun writeReleaseNotesToFile(releaseNotes: LibraryReleaseNotes) {
+ if (!project.getReleaseNotesDirectory().exists()) {
+ if (!project.getReleaseNotesDirectory().mkdirs()) {
+ throw RuntimeException("Failed to create " +
+ "output directory: ${project.getReleaseNotesDirectory()}")
+ }
+ }
+ var resolvedOutputFile: File = outputFile.get()
+ if (!resolvedOutputFile.exists()) {
+ if (!resolvedOutputFile.createNewFile()) {
+ throw RuntimeException("Failed to create " +
+ "output dependency dump file: $outputFile")
+ }
+ }
+ resolvedOutputFile.writeText(releaseNotes.toString())
+ }
+
+ /**
+ * Given the [GenerateReleaseNotesTask.startSHA], [GenerateReleaseNotesTask.endSHA], and
+ * [GenerateReleaseNotesTask.date], creates release notes for this specific project, starting
+ * at [GenerateReleaseNotesTask.startSHA] and ending at [GenerateReleaseNotesTask.endSHA].
+ */
+ @TaskAction
+ fun createReleaseNotes() {
+ if (startSHA.isEmpty()) {
+ throw RuntimeException("The generate release notes task need a start SHA from" +
+ "which to start generating release notes. You can pass it a start SHA by" +
+ "adding the argument -PstartCommit=<yourSHA>")
+ }
+
+ val commitList: List<Commit> = GitClientImpl(project.rootDir).getGitLog(
+ startSHA,
+ endSHA,
+ keepMerges,
+ project.projectDir
+ )
+
+ val releaseNotes = LibraryReleaseNotes(
+ project.group.toString(),
+ mutableListOf(project.name.toString()),
+ project.version.toString(),
+ date,
+ startSHA,
+ endSHA,
+ getProjectSpecificDirectory()
+ )
+
+ if (commitList.isEmpty()) {
+ logger.warn("WARNING: Found no commits for ${project.group}:${project.name} from " +
+ "start SHA $startSHA to end SHA $endSHA. To double check, you can run " +
+ "`git log --no-merges $startSHA..$endSHA ${project.projectDir}`")
+ }
+
+ commitList.forEach { commit ->
+ if (!isExcludedAuthorEmail(commit.authorEmail)) {
+ releaseNotes.addCommit(commit)
+ }
+ }
+
+ writeReleaseNotesToFile(releaseNotes)
+ }
+}
\ No newline at end of file
diff --git a/buildSrc/src/main/kotlin/androidx/build/releasenotes/Markdown.kt b/buildSrc/src/main/kotlin/androidx/build/releasenotes/Markdown.kt
new file mode 100644
index 0000000..498e836
--- /dev/null
+++ b/buildSrc/src/main/kotlin/androidx/build/releasenotes/Markdown.kt
@@ -0,0 +1,96 @@
+/*
+ * Copyright (C) 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.build.releasenotes
+
+/** Classes for generating markdown */
+
+enum class HeaderType {
+ H1, H2, H3, H4, H5, H6;
+ companion object {
+ fun getHeaderTag(tag: HeaderType): String {
+ when (tag) {
+ H1 -> return("#")
+ H2 -> return("##")
+ H3 -> return("###")
+ H4 -> return("####")
+ H5 -> return("#####")
+ H6 -> return("######")
+ }
+ }
+ }
+}
+
+open class MarkdownHeader {
+ var markdownType: HeaderType = HeaderType.H1
+ var text: String = ""
+
+ @Override
+ override fun toString(): String {
+ return HeaderType.getHeaderTag(markdownType) + ' ' + text
+ }
+ fun print() {
+ println(toString())
+ }
+}
+
+open class MarkdownLink {
+ var linkText: String = ""
+ var linkUrl: String = ""
+
+ @Override
+ override fun toString(): String {
+ return "([$linkText]($linkUrl))"
+ }
+
+ fun print() {
+ println(toString())
+ }
+}
+
+open class MarkdownBoldText(
+ inputText: String
+) {
+ var text: String = ""
+ init {
+ text = inputText
+ }
+
+ override fun toString(): String {
+ return "**$text**"
+ }
+
+ fun print() {
+ println(toString())
+ }
+}
+
+open class MarkdownComment(
+ inputText: String
+) {
+ var text: String = ""
+ init {
+ text = inputText
+ }
+
+ override fun toString(): String {
+ return "{# $text #}"
+ }
+
+ fun print() {
+ println(toString())
+ }
+}
\ No newline at end of file
diff --git a/buildSrc/src/main/kotlin/androidx/build/releasenotes/ReleaseNoteMarkdown.kt b/buildSrc/src/main/kotlin/androidx/build/releasenotes/ReleaseNoteMarkdown.kt
new file mode 100644
index 0000000..c4856c1
--- /dev/null
+++ b/buildSrc/src/main/kotlin/androidx/build/releasenotes/ReleaseNoteMarkdown.kt
@@ -0,0 +1,257 @@
+/*
+ * Copyright (C) 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.build.releasenotes
+
+import androidx.build.gitclient.Commit
+import androidx.build.gitclient.CommitType
+import java.time.LocalDate
+import java.time.format.DateTimeFormatter
+
+/**
+ * Classes for generating androidx release note specific markdown
+ */
+
+/**
+ * Markdown class for a Library Header in the format:
+ * ## Version <version> {:#<version>}
+ */
+class LibraryHeader(groupId: String, version: String) : MarkdownHeader() {
+ init {
+ markdownType = HeaderType.H2
+ text = "$groupId Version $version {:#$version}"
+ }
+}
+
+/**
+ * Generates the markdown list of commits with sections defined by enum [CommitType], in the format:
+ *
+ * **New Features**
+ *
+ * - <[Commit.summary]> <[getAOSPLink]> <[getBuganizerLink] 1> <[getBuganizerLink] 2>...
+ *
+ * **API Changes**
+ *
+ * - <[Commit.summary]> <[getAOSPLink]> <[getBuganizerLink] 1> <[getBuganizerLink] 2>...
+ *
+ * **Bug Fixes**
+ *
+ * - <[Commit.summary]> <[getAOSPLink]> <[getBuganizerLink] 1> <[getBuganizerLink] 2>...
+ *
+ * **External Contribution**
+ *
+ * - <[Commit.summary]> <[getAOSPLink]> <[getBuganizerLink] 1> <[getBuganizerLink] 2>...
+ *
+ */
+class CommitMarkdownList {
+ private var commits: MutableList<Commit> = mutableListOf()
+
+ fun add(commit: Commit) {
+ commits.add(commit)
+ }
+
+ fun getListItemStr(): String {
+ return "- "
+ }
+
+ private fun makeReleaseNotesSection(sectionCommitType: CommitType): String {
+ var sectionHeader: MarkdownBoldText = MarkdownBoldText(CommitType
+ .getTitle(sectionCommitType))
+ var markdownStringSection: String = ""
+ commits.filter { commit ->
+ commit.type == sectionCommitType
+ }.forEach { commit ->
+ markdownStringSection = "$markdownStringSection${getListItemStr()}$commit"
+ if (markdownStringSection.last() != '\n') {
+ markdownStringSection += '\n'
+ }
+ }
+ markdownStringSection = if (markdownStringSection.isEmpty()) {
+ "\n${MarkdownComment(sectionHeader.toString())}\n\n$markdownStringSection"
+ } else {
+ "\n$sectionHeader\n\n$markdownStringSection"
+ }
+ return markdownStringSection
+ }
+
+ @Override
+ override fun toString(): String {
+ var markdownString: String = ""
+ CommitType.values().forEach { commitType ->
+ markdownString += makeReleaseNotesSection(commitType)
+ }
+ return markdownString
+ }
+
+ fun print() {
+ println(toString())
+ }
+}
+
+/**
+ * @param startSHA the SHA at which to start the diff log (exclusive)
+ * @param endSHA the last SHA to include in the diff log (inclusive)
+ * @param projectDir the local directory of the project, in relation to frameworks/support
+ *
+ * @return A [MarkdownLink] to the public Gitiles diff log
+ */
+fun getGitilesDiffLogLink(startSHA: String, endSHA: String, projectDir: String): MarkdownLink {
+ val baseGitilesUrl: String =
+ "https://android.googlesource.com/platform/frameworks/support/+log/"
+ /* The root project directory is already existent in the url path, so the directory here
+ * should be relative to frameworks/support/.
+ */
+ if (projectDir.contains("frameworks/support")) {
+ throw RuntimeException("Gitiles directory should only contain the directory structure" +
+ "within frameworks/support/*, but received incorrect directory: $projectDir")
+ }
+ // Remove extra preceeding directory slashes, if they exist
+ var verifiedProjectDir = projectDir
+ while (verifiedProjectDir.first() == '/') {
+ verifiedProjectDir = verifiedProjectDir.removePrefix("/")
+ }
+ var gitilesLink: MarkdownLink = MarkdownLink()
+ gitilesLink.linkText = "here"
+ gitilesLink.linkUrl = "$baseGitilesUrl$startSHA..$endSHA/$verifiedProjectDir"
+ return gitilesLink
+}
+
+/**
+ * @param changeId The Gerrit Change-Id to link to
+ * @return A [MarkdownLink] to AOSP Gerrit
+ */
+fun getAOSPLink(changeId: String): MarkdownLink {
+ val baseAOSPUrl: String = "https://android-review.googlesource.com/#/q/"
+ var aospLink: MarkdownLink = MarkdownLink()
+ aospLink.linkText = "aosp/" + changeId.take(6)
+ aospLink.linkUrl = "$baseAOSPUrl$changeId"
+ return aospLink
+}
+
+/**
+ * @param bugId the Id of the buganizer issue
+ * @return A [MarkdownLink] to the public buganizer issue tracker
+ *
+ * Note: This method does not check if the bug is public
+ */
+fun getBuganizerLink(bugId: Int): MarkdownLink {
+ val baseBuganizerUrl: String = "https://issuetracker.google.com/issues/"
+ var buganizerLink: MarkdownLink = MarkdownLink()
+ buganizerLink.linkText = "b/$bugId"
+ buganizerLink.linkUrl = "$baseBuganizerUrl$bugId"
+ return buganizerLink
+}
+
+/**
+ * Structured release notes class, that connects all parts of the release notes. Create release
+ * notes in the format:
+ * <pre>
+ * <[LibraryHeader]>
+ * <Date>
+ *
+ * `androidx.<groupId>:<artifactId>:<version>` is released. The commits included in this version
+ * can be found <[MarkdownLink]>.
+ *
+ * <[CommitMarkdownList]>
+ * </pre>
+ *
+ * @property groupId Library GroupId.
+ * @property artifactIds List of ArtifactIds included in these release notes.
+ * @property version Version of the library, assuming all artifactIds have the same version.
+ * @property releaseDate Date the release will go live. Defaults to the current date.
+ * @param startSHA The first SHA to include in the release notes.
+ * @param endSHA The last SHA to be included in the release notes.
+ * @param projectDir The filepath relative to the parent directory of the .git directory.
+ */
+class LibraryReleaseNotes(
+ private val groupId: String,
+ private val artifactIds: MutableList<String>,
+ private val version: String,
+ private val releaseDate: LocalDate,
+ startSHA: String,
+ endSHA: String,
+ projectDir: String
+) {
+
+ private var diffLogLink: MarkdownLink
+ private var header: LibraryHeader
+ private var commits: MutableList<Commit> = mutableListOf()
+ private var commitMarkdownList: CommitMarkdownList = CommitMarkdownList()
+ private var summary: String = ""
+ private var bugsFixed: MutableList<Int> = mutableListOf()
+
+ init {
+ if (version == "" || groupId == "") {
+ throw RuntimeException("Tried to create Library Release Notes Header without setting" +
+ "the groupId or version!")
+ }
+ if (startSHA == "" || endSHA == "") {
+ throw RuntimeException("Tried to create Library Release Notes with an empty SHA!")
+ }
+ header = LibraryHeader(groupId, version)
+ diffLogLink = getGitilesDiffLogLink(startSHA, endSHA, projectDir)
+ }
+
+ fun getFormattedDate(): String {
+ val formatter = DateTimeFormatter.ofPattern("MMMM d, yyyy")
+ return formatter.format(releaseDate)
+ }
+
+ fun getFormattedReleaseSummary(): String {
+ val numberArtifacts = artifactIds.size
+ for (i: Int in 0..(numberArtifacts - 1)) {
+ var currentArtifactId: String = artifactIds[i]
+ when (numberArtifacts) {
+ 1 -> {
+ summary = "`$groupId:$currentArtifactId:$version` is released. "
+ }
+ 2 -> {
+ if (i == 0) {
+ summary = "`$groupId:$currentArtifactId:$version` and "
+ }
+ if (i == 1) {
+ summary += "`$groupId:$currentArtifactId:$version` are released. "
+ }
+ }
+ else -> {
+ if (i < numberArtifacts - 1) {
+ summary += "`$groupId:$currentArtifactId:$version`, "
+ } else {
+ summary += "and `$groupId:$currentArtifactId:$version` are released. "
+ }
+ }
+ }
+ }
+
+ summary += "The commits included in this version can be found $diffLogLink.\n"
+ return summary
+ }
+
+ fun addCommit(newCommit: Commit) {
+ newCommit.bugs.forEach { bug ->
+ bugsFixed.add(bug)
+ }
+ commits.add(newCommit)
+ commitMarkdownList.add(newCommit)
+ }
+
+ override fun toString(): String {
+ return "$header\n" +
+ "${getFormattedDate()}\n\n" +
+ getFormattedReleaseSummary() +
+ "$commitMarkdownList"
+ }
+}
\ No newline at end of file
diff --git a/camera/camera-camera2/build.gradle b/camera/camera-camera2/build.gradle
index 2a15464..b13de44 100644
--- a/camera/camera-camera2/build.gradle
+++ b/camera/camera-camera2/build.gradle
@@ -27,7 +27,7 @@
dependencies {
api(project(":camera:camera-core"))
- implementation("androidx.core:core:1.1.0-rc01")
+ implementation("androidx.core:core:1.1.0")
implementation("androidx.annotation:annotation:1.0.0")
implementation("androidx.concurrent:concurrent-futures:1.0.0-alpha03")
implementation(GUAVA_LISTENABLE_FUTURE)
diff --git a/camera/camera-camera2/src/androidTest/java/androidx/camera/camera2/ImageCaptureTest.java b/camera/camera-camera2/src/androidTest/java/androidx/camera/camera2/ImageCaptureTest.java
index 308491b..c88026a 100644
--- a/camera/camera-camera2/src/androidTest/java/androidx/camera/camera2/ImageCaptureTest.java
+++ b/camera/camera-camera2/src/androidTest/java/androidx/camera/camera2/ImageCaptureTest.java
@@ -44,6 +44,7 @@
import android.util.Size;
import android.view.Surface;
+import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.camera.camera2.impl.util.FakeRepeatingUseCase;
import androidx.camera.core.AppConfig;
@@ -58,10 +59,10 @@
import androidx.camera.core.CaptureStage;
import androidx.camera.core.Exif;
import androidx.camera.core.ImageCapture;
+import androidx.camera.core.ImageCapture.ImageCaptureError;
import androidx.camera.core.ImageCapture.Metadata;
import androidx.camera.core.ImageCapture.OnImageCapturedListener;
import androidx.camera.core.ImageCapture.OnImageSavedListener;
-import androidx.camera.core.ImageCapture.UseCaseError;
import androidx.camera.core.ImageCaptureConfig;
import androidx.camera.core.ImageProxy;
import androidx.camera.testing.CameraUtil;
@@ -188,7 +189,7 @@
mOnImageSavedListener =
new OnImageSavedListener() {
@Override
- public void onImageSaved(File file) {
+ public void onImageSaved(@NonNull File file) {
mMockImageSavedListener.onImageSaved(file);
// Signal that an image was saved
mSemaphore.release();
@@ -196,7 +197,8 @@
@Override
public void onError(
- UseCaseError error, String message, @Nullable Throwable cause) {
+ @NonNull ImageCaptureError error, @NonNull String message,
+ @Nullable Throwable cause) {
mMockImageSavedListener.onError(error, message, cause);
// Signal that there was an error
mSemaphore.release();
@@ -465,7 +467,7 @@
mSemaphore.acquire();
verify(mMockImageSavedListener)
- .onError(eq(UseCaseError.FILE_IO_ERROR), anyString(), any(Throwable.class));
+ .onError(eq(ImageCaptureError.FILE_IO_ERROR), anyString(), any(Throwable.class));
}
@Suppress // TODO(b/133171096): Remove once this no longer throws an IllegalStateException
@@ -601,7 +603,7 @@
OnImageCapturedListener mockOnImageCaptureListener = mock(OnImageCapturedListener.class);
imageCapture.takePicture(mockOnImageCaptureListener);
- verify(mockOnImageCaptureListener, timeout(3000)).onError(any(UseCaseError.class),
+ verify(mockOnImageCaptureListener, timeout(3000)).onError(any(ImageCaptureError.class),
anyString(), any(IllegalArgumentException.class));
CameraX.unbind(imageCapture);
@@ -643,8 +645,8 @@
imageCapture.takePicture(mockOnImageCaptureListener);
// It should get onError() callback twice.
- verify(mockOnImageCaptureListener, timeout(3000).times(2)).onError(any(UseCaseError.class),
- anyString(), any(IllegalArgumentException.class));
+ verify(mockOnImageCaptureListener, timeout(3000).times(2)).onError(
+ any(ImageCaptureError.class), anyString(), any(IllegalArgumentException.class));
CameraX.unbind(imageCapture);
}
diff --git a/camera/camera-camera2/src/androidTest/java/androidx/camera/camera2/ImageReaderProxysTest.java b/camera/camera-camera2/src/androidTest/java/androidx/camera/camera2/ImageReaderProxysTest.java
index 3f31ab0..b337735 100644
--- a/camera/camera-camera2/src/androidTest/java/androidx/camera/camera2/ImageReaderProxysTest.java
+++ b/camera/camera-camera2/src/androidTest/java/androidx/camera/camera2/ImageReaderProxysTest.java
@@ -25,10 +25,9 @@
import android.os.HandlerThread;
import android.util.Size;
-import androidx.camera.core.AppConfig;
+import androidx.camera.camera2.impl.Camera2CameraFactory;
import androidx.camera.core.BaseCamera;
import androidx.camera.core.CameraFactory;
-import androidx.camera.core.CameraX;
import androidx.camera.core.ImageProxy;
import androidx.camera.core.ImageReaderProxy;
import androidx.camera.core.ImageReaderProxys;
@@ -44,6 +43,7 @@
import org.junit.After;
import org.junit.Before;
+import org.junit.BeforeClass;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -51,6 +51,7 @@
import java.util.HashMap;
import java.util.List;
import java.util.Map;
+import java.util.concurrent.ExecutionException;
import java.util.concurrent.Semaphore;
/**
@@ -60,7 +61,8 @@
@RunWith(AndroidJUnit4.class)
public final class ImageReaderProxysTest {
private static final String CAMERA_ID = "0";
- private static final int TEST_TIMEOUT_MILLIS = 3000;
+
+ private static CameraFactory sCameraFactory;
private BaseCamera mCamera;
private HandlerThread mHandlerThread;
@@ -81,27 +83,37 @@
};
}
+ @BeforeClass
+ public static void initializeFactory() {
+ Context context = ApplicationProvider.getApplicationContext();
+ sCameraFactory = new Camera2CameraFactory(context);
+ }
+
@Before
public void setUp() {
assumeTrue(CameraUtil.deviceHasCamera());
- Context context = ApplicationProvider.getApplicationContext();
- AppConfig appConfig = Camera2AppConfig.create(context);
- CameraFactory cameraFactory = appConfig.getCameraFactory(null);
- CameraX.init(context, appConfig);
- mCamera = cameraFactory.getCamera(CAMERA_ID);
+
mHandlerThread = new HandlerThread("Background");
mHandlerThread.start();
mHandler = new Handler(mHandlerThread.getLooper());
mReaders = new ArrayList<>();
+
+ // Grab the camera so we can wait for release in tearDown()
+
+ mCamera = sCameraFactory.getCamera(CAMERA_ID);
}
@After
- public void tearDown() {
+ public void tearDown() throws ExecutionException, InterruptedException {
for (ImageReaderProxy reader : mReaders) {
reader.close();
}
- if (mCamera != null && mHandlerThread != null) {
- mCamera.release();
+
+ if (mCamera != null) {
+ mCamera.release().get();
+ }
+
+ if (mHandlerThread != null) {
mHandlerThread.quitSafely();
}
}
diff --git a/camera/camera-camera2/src/androidTest/java/androidx/camera/camera2/PreviewTest.java b/camera/camera-camera2/src/androidTest/java/androidx/camera/camera2/PreviewTest.java
index 002859c..3d2bf9c 100644
--- a/camera/camera-camera2/src/androidTest/java/androidx/camera/camera2/PreviewTest.java
+++ b/camera/camera-camera2/src/androidTest/java/androidx/camera/camera2/PreviewTest.java
@@ -20,7 +20,6 @@
import static com.google.common.truth.Truth.assertWithMessage;
import static org.junit.Assume.assumeTrue;
-import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
@@ -34,7 +33,6 @@
import android.view.Surface;
import androidx.annotation.NonNull;
-import androidx.camera.camera2.impl.Camera2CameraControl;
import androidx.camera.core.AppConfig;
import androidx.camera.core.CameraControlInternal;
import androidx.camera.core.CameraFactory;
@@ -42,14 +40,13 @@
import androidx.camera.core.CameraX.LensFacing;
import androidx.camera.core.CaptureConfig;
import androidx.camera.core.DeferrableSurfaces;
-import androidx.camera.core.OnFocusListener;
import androidx.camera.core.Preview;
import androidx.camera.core.Preview.OnPreviewOutputUpdateListener;
import androidx.camera.core.Preview.PreviewOutput;
import androidx.camera.core.PreviewConfig;
import androidx.camera.core.SessionConfig;
-import androidx.camera.core.impl.utils.executor.CameraXExecutors;
import androidx.camera.testing.CameraUtil;
+import androidx.camera.testing.fakes.FakeCameraControl;
import androidx.test.annotation.UiThreadTest;
import androidx.test.core.app.ApplicationProvider;
import androidx.test.ext.junit.runners.AndroidJUnit4;
@@ -67,7 +64,6 @@
import java.util.List;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
-import java.util.concurrent.Executor;
import java.util.concurrent.FutureTask;
import java.util.concurrent.Semaphore;
import java.util.concurrent.TimeUnit;
@@ -139,26 +135,6 @@
@Test
@UiThreadTest
- public void focusRegionCanBeSet() {
- Preview useCase = new Preview(mDefaultConfig);
- useCase.updateSuggestedResolution(Collections.singletonMap(mCameraId, DEFAULT_RESOLUTION));
-
- CameraControlInternal cameraControl = mock(CameraControlInternal.class);
- useCase.attachCameraControl(mCameraId, cameraControl);
-
- Rect rect = new Rect(/*left=*/ 200, /*top=*/ 200, /*right=*/ 800, /*bottom=*/ 800);
- useCase.focus(rect, rect, mock(OnFocusListener.class));
-
- ArgumentCaptor<Rect> rectArgumentCaptor1 = ArgumentCaptor.forClass(Rect.class);
- ArgumentCaptor<Rect> rectArgumentCaptor2 = ArgumentCaptor.forClass(Rect.class);
- verify(cameraControl).focus(rectArgumentCaptor1.capture(), rectArgumentCaptor2.capture(),
- any(Executor.class), any(OnFocusListener.class));
- assertThat(rectArgumentCaptor1.getValue()).isEqualTo(rect);
- assertThat(rectArgumentCaptor2.getValue()).isEqualTo(rect);
- }
-
- @Test
- @UiThreadTest
public void zoomRegionCanBeSet() {
Preview useCase = new Preview(mDefaultConfig);
useCase.updateSuggestedResolution(Collections.singletonMap(mCameraId, DEFAULT_RESOLUTION));
@@ -490,21 +466,16 @@
}
private CameraControlInternal getFakeCameraControl() {
- return new Camera2CameraControl(
- new CameraControlInternal.ControlUpdateListener() {
- @Override
- public void onCameraControlUpdateSessionConfig(
- @NonNull SessionConfig sessionConfig) {
- }
+ return new FakeCameraControl(new CameraControlInternal.ControlUpdateListener() {
+ @Override
+ public void onCameraControlUpdateSessionConfig(@NonNull SessionConfig sessionConfig) {
+ }
- @Override
- public void onCameraControlCaptureRequests(
- @NonNull List<CaptureConfig> captureConfigs) {
-
- }
- },
- CameraXExecutors.mainThreadExecutor(),
- CameraXExecutors.mainThreadExecutor());
+ @Override
+ public void onCameraControlCaptureRequests(
+ @NonNull List<CaptureConfig> captureConfigs) {
+ }
+ });
}
private static final class SurfaceTextureCallable implements Callable<SurfaceTexture> {
diff --git a/camera/camera-camera2/src/androidTest/java/androidx/camera/camera2/SensorOrientedMeteringPointFactoryTest.java b/camera/camera-camera2/src/androidTest/java/androidx/camera/camera2/SensorOrientedMeteringPointFactoryTest.java
new file mode 100644
index 0000000..0e5ee3e
--- /dev/null
+++ b/camera/camera-camera2/src/androidTest/java/androidx/camera/camera2/SensorOrientedMeteringPointFactoryTest.java
@@ -0,0 +1,134 @@
+/*
+ * 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.camera.camera2;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.content.Context;
+import android.os.Handler;
+import android.os.Looper;
+import android.util.Rational;
+
+import androidx.camera.core.AppConfig;
+import androidx.camera.core.CameraX;
+import androidx.camera.core.ImageAnalysis;
+import androidx.camera.core.ImageAnalysisConfig;
+import androidx.camera.core.MeteringPoint;
+import androidx.camera.core.MeteringPointFactory;
+import androidx.camera.core.SensorOrientedMeteringPointFactory;
+import androidx.camera.testing.fakes.FakeLifecycleOwner;
+import androidx.lifecycle.LifecycleOwner;
+import androidx.test.core.app.ApplicationProvider;
+
+import org.junit.Before;
+import org.junit.Test;
+
+public final class SensorOrientedMeteringPointFactoryTest {
+ private static final float WIDTH = 480;
+ private static final float HEIGHT = 640;
+ private LifecycleOwner mLifecycle;
+ SensorOrientedMeteringPointFactory mPointFactory;
+ @Before
+ public void setUp() {
+ Context context = ApplicationProvider.getApplicationContext();
+ AppConfig config = Camera2AppConfig.create(context);
+
+ CameraX.init(context, config);
+ mLifecycle = new FakeLifecycleOwner();
+ mPointFactory = new SensorOrientedMeteringPointFactory(WIDTH, HEIGHT);
+ }
+
+ @Test
+ public void defaultWeightAndAreaSize() {
+ MeteringPoint point = mPointFactory.createPoint(0, 0);
+ assertThat(point.getSize()).isEqualTo(MeteringPointFactory.DEFAULT_AREASIZE);
+ assertThat(point.getWeight()).isEqualTo(MeteringPointFactory.DEFAULT_WEIGHT);
+ assertThat(point.getFOVAspectRatio()).isNull();
+ }
+
+ @Test
+ public void createPointWithValidWeightAndAreaSize() {
+ final float areaSize = 0.2f;
+ final float weight = 0.5f;
+ MeteringPoint point = mPointFactory.createPoint(0, 0, areaSize, weight);
+ assertThat(point.getSize()).isEqualTo(areaSize);
+ assertThat(point.getWeight()).isEqualTo(weight);
+ assertThat(point.getFOVAspectRatio()).isNull();
+ }
+
+ @Test
+ public void createPointLeftTop_correctValueSet() {
+ MeteringPoint meteringPoint = mPointFactory.createPoint(0f, 0f);
+ assertThat(meteringPoint.getNormalizedCropRegionX()).isEqualTo(0f);
+ assertThat(meteringPoint.getNormalizedCropRegionY()).isEqualTo(0f);
+ }
+
+ @Test
+ public void createPointLeftBottom_correctValueSet() {
+ MeteringPoint meteringPoint2 = mPointFactory.createPoint(0f, HEIGHT);
+ assertThat(meteringPoint2.getNormalizedCropRegionX()).isEqualTo(0f);
+ assertThat(meteringPoint2.getNormalizedCropRegionY()).isEqualTo(1f);
+ }
+
+ @Test
+ public void createPointRightTop_correctValueSet() {
+ MeteringPoint meteringPoint3 = mPointFactory.createPoint(WIDTH, 0f);
+ assertThat(meteringPoint3.getNormalizedCropRegionX()).isEqualTo(1f);
+ assertThat(meteringPoint3.getNormalizedCropRegionY()).isEqualTo(0f);
+ }
+
+ @Test
+ public void createPointRightBottom_correctValueSet() {
+ MeteringPoint meteringPoint4 = mPointFactory.createPoint(WIDTH, HEIGHT);
+ assertThat(meteringPoint4.getNormalizedCropRegionX()).isEqualTo(1f);
+ assertThat(meteringPoint4.getNormalizedCropRegionY()).isEqualTo(1f);
+ }
+
+ @Test
+ public void createPointWithFoVUseCase_success() {
+ ImageAnalysisConfig imageAnalysisConfig =
+ new ImageAnalysisConfig.Builder()
+ .setLensFacing(CameraX.LensFacing.BACK)
+ .setTargetAspectRatio(new Rational(3, 4))
+ .setTargetName("ImageAnalysis")
+ .setCallbackHandler(new Handler(Looper.getMainLooper()))
+ .build();
+ ImageAnalysis imageAnalysis = new ImageAnalysis(imageAnalysisConfig);
+ CameraX.bindToLifecycle(mLifecycle, imageAnalysis);
+
+ SensorOrientedMeteringPointFactory factory = new SensorOrientedMeteringPointFactory(
+ WIDTH, HEIGHT, imageAnalysis);
+ MeteringPoint point = factory.createPoint(0f, 0f);
+ assertThat(point.getFOVAspectRatio()).isEqualTo(new Rational(4, 3));
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void createPointWithFoVUseCase_FailedNotBound() {
+ ImageAnalysisConfig imageAnalysisConfig =
+ new ImageAnalysisConfig.Builder()
+ .setLensFacing(CameraX.LensFacing.BACK)
+ .setTargetAspectRatio(new Rational(3, 4))
+ .setTargetName("ImageAnalysis")
+ .setCallbackHandler(new Handler(Looper.getMainLooper()))
+ .build();
+ ImageAnalysis imageAnalysis = new ImageAnalysis(imageAnalysisConfig);
+
+ // This will throw IllegalStateException.
+ SensorOrientedMeteringPointFactory factory = new SensorOrientedMeteringPointFactory(
+ WIDTH, HEIGHT, imageAnalysis);
+ }
+}
diff --git a/camera/camera-camera2/src/androidTest/java/androidx/camera/camera2/impl/Camera2CameraControlTest.java b/camera/camera-camera2/src/androidTest/java/androidx/camera/camera2/impl/Camera2CameraControlTest.java
index 3cd0401..7ce5de0 100644
--- a/camera/camera-camera2/src/androidTest/java/androidx/camera/camera2/impl/Camera2CameraControlTest.java
+++ b/camera/camera-camera2/src/androidTest/java/androidx/camera/camera2/impl/Camera2CameraControlTest.java
@@ -20,31 +20,47 @@
import static android.hardware.camera2.CameraMetadata.CONTROL_AE_MODE_ON;
import static android.hardware.camera2.CameraMetadata.CONTROL_AE_MODE_ON_ALWAYS_FLASH;
import static android.hardware.camera2.CameraMetadata.CONTROL_AE_MODE_ON_AUTO_FLASH;
+import static android.hardware.camera2.CameraMetadata.CONTROL_AF_MODE_AUTO;
+import static android.hardware.camera2.CameraMetadata.CONTROL_AF_MODE_CONTINUOUS_PICTURE;
+import static android.hardware.camera2.CameraMetadata.CONTROL_AF_MODE_OFF;
+import static android.hardware.camera2.CameraMetadata.CONTROL_AWB_MODE_AUTO;
+import static android.hardware.camera2.CameraMetadata.CONTROL_AWB_MODE_OFF;
import static android.hardware.camera2.CameraMetadata.FLASH_MODE_OFF;
import static android.hardware.camera2.CameraMetadata.FLASH_MODE_TORCH;
import static com.google.common.truth.Truth.assertThat;
+import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
import static org.mockito.Mockito.reset;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
+import android.content.Context;
import android.graphics.Rect;
+import android.hardware.camera2.CameraAccessException;
+import android.hardware.camera2.CameraCharacteristics;
+import android.hardware.camera2.CameraManager;
import android.hardware.camera2.CaptureRequest;
-import android.hardware.camera2.params.MeteringRectangle;
import android.os.Build;
import android.os.Handler;
import android.os.HandlerThread;
import androidx.camera.camera2.Camera2Config;
import androidx.camera.core.CameraControlInternal;
+import androidx.camera.core.CameraInfoUnavailableException;
+import androidx.camera.core.CameraX;
import androidx.camera.core.CaptureConfig;
import androidx.camera.core.FlashMode;
+import androidx.camera.core.FocusMeteringAction;
+import androidx.camera.core.FocusMeteringAction.MeteringMode;
+import androidx.camera.core.SensorOrientedMeteringPointFactory;
import androidx.camera.core.SessionConfig;
import androidx.camera.core.impl.utils.executor.CameraXExecutors;
import androidx.camera.testing.HandlerUtil;
import androidx.core.os.HandlerCompat;
+import androidx.test.core.app.ApplicationProvider;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
@@ -53,6 +69,7 @@
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
+import org.mockito.Mockito;
import java.util.Collections;
import java.util.List;
@@ -61,8 +78,6 @@
@SmallTest
@RunWith(AndroidJUnit4.class)
public final class Camera2CameraControlTest {
-
- private static final long NO_TIMEOUT = 0;
private Camera2CameraControl mCamera2CameraControl;
private CameraControlInternal.ControlUpdateListener mControlUpdateListener;
private ArgumentCaptor<SessionConfig> mSessionConfigArgumentCaptor =
@@ -72,17 +87,24 @@
ArgumentCaptor.forClass(List.class);
private HandlerThread mHandlerThread;
private Handler mHandler;
+ private CameraCharacteristics mCameraCharacteristics;
@Before
- public void setUp() throws InterruptedException {
+ public void setUp() throws InterruptedException, CameraAccessException,
+ CameraInfoUnavailableException {
+ Context context = ApplicationProvider.getApplicationContext();
+ CameraManager cameraManager = (CameraManager) context.getSystemService(
+ Context.CAMERA_SERVICE);
+ mCameraCharacteristics = cameraManager.getCameraCharacteristics(
+ CameraX.getCameraWithLensFacing(CameraX.LensFacing.BACK));
mControlUpdateListener = mock(CameraControlInternal.ControlUpdateListener.class);
mHandlerThread = new HandlerThread("ControlThread");
mHandlerThread.start();
mHandler = HandlerCompat.createAsync(mHandlerThread.getLooper());
ScheduledExecutorService executorService = CameraXExecutors.newHandlerExecutor(mHandler);
- mCamera2CameraControl = new Camera2CameraControl(mControlUpdateListener, NO_TIMEOUT,
- executorService, executorService);
+ mCamera2CameraControl = new Camera2CameraControl(mCameraCharacteristics,
+ mControlUpdateListener, executorService, executorService);
HandlerUtil.waitForLooperToIdle(mHandler);
@@ -117,183 +139,15 @@
}
@Test
- public void focus_focusRectSetAndRequestsExecuted() throws InterruptedException {
- Rect focusRect = new Rect(0, 0, 10, 10);
- Rect meteringRect = new Rect(20, 20, 30, 30);
-
- mCamera2CameraControl.focus(focusRect, meteringRect);
-
- HandlerUtil.waitForLooperToIdle(mHandler);
-
- verify(mControlUpdateListener, times(1)).onCameraControlUpdateSessionConfig(
- mSessionConfigArgumentCaptor.capture());
- SessionConfig sessionConfig = mSessionConfigArgumentCaptor.getValue();
- Camera2Config repeatingConfig = new Camera2Config(sessionConfig.getImplementationOptions());
-
- assertThat(
- repeatingConfig.getCaptureRequestOption(
- CaptureRequest.CONTROL_AF_REGIONS, null))
- .isEqualTo(
- new MeteringRectangle[]{
- new MeteringRectangle(focusRect,
- MeteringRectangle.METERING_WEIGHT_MAX)
- });
-
- assertThat(
- repeatingConfig.getCaptureRequestOption(
- CaptureRequest.CONTROL_AE_REGIONS, null))
- .isEqualTo(
- new MeteringRectangle[]{
- new MeteringRectangle(
- meteringRect, MeteringRectangle.METERING_WEIGHT_MAX)
- });
-
- assertThat(
- repeatingConfig.getCaptureRequestOption(
- CaptureRequest.CONTROL_AWB_REGIONS, null))
- .isEqualTo(
- new MeteringRectangle[]{
- new MeteringRectangle(
- meteringRect, MeteringRectangle.METERING_WEIGHT_MAX)
- });
-
- Camera2Config singleConfig = new Camera2Config(mCamera2CameraControl.getSharedOptions());
- assertThat(
- singleConfig.getCaptureRequestOption(
- CaptureRequest.CONTROL_AF_REGIONS, null))
- .isEqualTo(
- new MeteringRectangle[]{
- new MeteringRectangle(focusRect,
- MeteringRectangle.METERING_WEIGHT_MAX)
- });
-
- assertThat(
- singleConfig.getCaptureRequestOption(
- CaptureRequest.CONTROL_AE_REGIONS, null))
- .isEqualTo(
- new MeteringRectangle[]{
- new MeteringRectangle(
- meteringRect, MeteringRectangle.METERING_WEIGHT_MAX)
- });
-
- assertThat(
- singleConfig.getCaptureRequestOption(
- CaptureRequest.CONTROL_AWB_REGIONS, null))
- .isEqualTo(
- new MeteringRectangle[]{
- new MeteringRectangle(
- meteringRect, MeteringRectangle.METERING_WEIGHT_MAX)
- });
-
- assertThat(mCamera2CameraControl.isFocusLocked()).isTrue();
-
- verify(mControlUpdateListener).onCameraControlCaptureRequests(
- mCaptureConfigArgumentCaptor.capture());
- CaptureConfig captureConfig = mCaptureConfigArgumentCaptor.getValue().get(0);
- Camera2Config resultCaptureConfig =
- new Camera2Config(captureConfig.getImplementationOptions());
-
- assertThat(resultCaptureConfig.getCaptureRequestOption(CaptureRequest.CONTROL_AF_TRIGGER,
- null)).isEqualTo(CaptureRequest.CONTROL_AF_TRIGGER_START);
- }
-
- @Test
- public void cancelFocus_regionRestored() throws InterruptedException {
- Rect focusRect = new Rect(0, 0, 10, 10);
- Rect meteringRect = new Rect(20, 20, 30, 30);
-
- mCamera2CameraControl.focus(focusRect, meteringRect);
- mCamera2CameraControl.cancelFocus();
-
- HandlerUtil.waitForLooperToIdle(mHandler);
-
- verify(mControlUpdateListener, times(2)).onCameraControlUpdateSessionConfig(
- mSessionConfigArgumentCaptor.capture());
- SessionConfig sessionConfig = mSessionConfigArgumentCaptor.getAllValues().get(1);
- Camera2Config repeatingConfig = new Camera2Config(sessionConfig.getImplementationOptions());
- MeteringRectangle zeroRegion =
- new MeteringRectangle(new Rect(), MeteringRectangle.METERING_WEIGHT_DONT_CARE);
-
- assertThat(
- repeatingConfig.getCaptureRequestOption(
- CaptureRequest.CONTROL_AF_REGIONS, null))
- .isEqualTo(new MeteringRectangle[]{zeroRegion});
- assertThat(
- repeatingConfig.getCaptureRequestOption(
- CaptureRequest.CONTROL_AE_REGIONS, null))
- .isEqualTo(new MeteringRectangle[]{zeroRegion});
- assertThat(
- repeatingConfig.getCaptureRequestOption(
- CaptureRequest.CONTROL_AWB_REGIONS, null))
- .isEqualTo(new MeteringRectangle[]{zeroRegion});
-
- Camera2Config singleConfig = new Camera2Config(mCamera2CameraControl.getSharedOptions());
- assertThat(
- singleConfig.getCaptureRequestOption(
- CaptureRequest.CONTROL_AF_REGIONS, null))
- .isEqualTo(new MeteringRectangle[]{zeroRegion});
- assertThat(
- singleConfig.getCaptureRequestOption(
- CaptureRequest.CONTROL_AE_REGIONS, null))
- .isEqualTo(new MeteringRectangle[]{zeroRegion});
- assertThat(
- singleConfig.getCaptureRequestOption(
- CaptureRequest.CONTROL_AWB_REGIONS, null))
- .isEqualTo(new MeteringRectangle[]{zeroRegion});
-
- assertThat(mCamera2CameraControl.isFocusLocked()).isFalse();
-
- verify(mControlUpdateListener, times(2)).onCameraControlCaptureRequests(
- mCaptureConfigArgumentCaptor.capture());
- CaptureConfig captureConfig = mCaptureConfigArgumentCaptor.getAllValues().get(1).get(0);
- Camera2Config resultCaptureConfig =
- new Camera2Config(captureConfig.getImplementationOptions());
-
- assertThat(resultCaptureConfig.getCaptureRequestOption(CaptureRequest.CONTROL_AF_TRIGGER,
- null)).isEqualTo(CaptureRequest.CONTROL_AF_TRIGGER_CANCEL);
- }
-
- @Test
public void defaultAFAWBMode_ShouldBeCAFWhenNotFocusLocked() {
Camera2Config singleConfig = new Camera2Config(mCamera2CameraControl.getSharedOptions());
assertThat(
singleConfig.getCaptureRequestOption(
CaptureRequest.CONTROL_MODE, CaptureRequest.CONTROL_MODE_OFF))
.isEqualTo(CaptureRequest.CONTROL_MODE_AUTO);
- assertThat(
- singleConfig.getCaptureRequestOption(
- CaptureRequest.CONTROL_AF_MODE, CaptureRequest.CONTROL_AF_MODE_OFF))
- .isEqualTo(CaptureRequest.CONTROL_AF_MODE_CONTINUOUS_PICTURE);
- assertThat(
- singleConfig.getCaptureRequestOption(
- CaptureRequest.CONTROL_AWB_MODE,
- CaptureRequest.CONTROL_AWB_MODE_OFF))
- .isEqualTo(CaptureRequest.CONTROL_AWB_MODE_AUTO);
- }
- @Test
- public void focus_afModeSetToAuto() throws InterruptedException {
- Rect focusRect = new Rect(0, 0, 10, 10);
- mCamera2CameraControl.focus(focusRect, focusRect);
-
- HandlerUtil.waitForLooperToIdle(mHandler);
-
- Camera2Config singleConfig = new Camera2Config(mCamera2CameraControl.getSharedOptions());
-
- mCamera2CameraControl.cancelFocus();
-
- HandlerUtil.waitForLooperToIdle(mHandler);
-
- Camera2Config singleConfig2 = new Camera2Config(mCamera2CameraControl.getSharedOptions());
-
- assertThat(
- singleConfig.getCaptureRequestOption(
- CaptureRequest.CONTROL_AF_MODE, CaptureRequest.CONTROL_AF_MODE_OFF))
- .isEqualTo(CaptureRequest.CONTROL_AF_MODE_AUTO);
- assertThat(
- singleConfig2.getCaptureRequestOption(
- CaptureRequest.CONTROL_AF_MODE, CaptureRequest.CONTROL_AF_MODE_OFF))
- .isEqualTo(CaptureRequest.CONTROL_AF_MODE_CONTINUOUS_PICTURE);
+ assertAfMode(singleConfig, CONTROL_AF_MODE_CONTINUOUS_PICTURE);
+ assertAwbMode(singleConfig, CONTROL_AWB_MODE_AUTO);
}
@Test
@@ -306,10 +160,8 @@
mSessionConfigArgumentCaptor.capture());
SessionConfig sessionConfig = mSessionConfigArgumentCaptor.getValue();
Camera2Config camera2Config = new Camera2Config(sessionConfig.getImplementationOptions());
- assertThat(
- camera2Config.getCaptureRequestOption(
- CaptureRequest.CONTROL_AE_MODE, CONTROL_AE_MODE_OFF))
- .isEqualTo(CONTROL_AE_MODE_ON_AUTO_FLASH);
+
+ assertAeMode(camera2Config, CONTROL_AE_MODE_ON_AUTO_FLASH);
assertThat(mCamera2CameraControl.getFlashMode()).isEqualTo(FlashMode.AUTO);
}
@@ -323,10 +175,9 @@
mSessionConfigArgumentCaptor.capture());
SessionConfig sessionConfig = mSessionConfigArgumentCaptor.getValue();
Camera2Config camera2Config = new Camera2Config(sessionConfig.getImplementationOptions());
- assertThat(
- camera2Config.getCaptureRequestOption(
- CaptureRequest.CONTROL_AE_MODE, CONTROL_AE_MODE_OFF))
- .isEqualTo(CaptureRequest.CONTROL_AE_MODE_ON);
+
+ assertAeMode(camera2Config, CONTROL_AE_MODE_ON);
+
assertThat(mCamera2CameraControl.getFlashMode()).isEqualTo(FlashMode.OFF);
}
@@ -340,10 +191,9 @@
mSessionConfigArgumentCaptor.capture());
SessionConfig sessionConfig = mSessionConfigArgumentCaptor.getValue();
Camera2Config camera2Config = new Camera2Config(sessionConfig.getImplementationOptions());
- assertThat(
- camera2Config.getCaptureRequestOption(
- CaptureRequest.CONTROL_AE_MODE, CONTROL_AE_MODE_OFF))
- .isEqualTo(CONTROL_AE_MODE_ON_ALWAYS_FLASH);
+
+ assertAeMode(camera2Config, CONTROL_AE_MODE_ON_ALWAYS_FLASH);
+
assertThat(mCamera2CameraControl.getFlashMode()).isEqualTo(FlashMode.ON);
}
@@ -357,10 +207,9 @@
mSessionConfigArgumentCaptor.capture());
SessionConfig sessionConfig = mSessionConfigArgumentCaptor.getValue();
Camera2Config camera2Config = new Camera2Config(sessionConfig.getImplementationOptions());
- assertThat(
- camera2Config.getCaptureRequestOption(
- CaptureRequest.CONTROL_AE_MODE, CONTROL_AE_MODE_OFF))
- .isEqualTo(CONTROL_AE_MODE_ON);
+
+ assertAeMode(camera2Config, CONTROL_AE_MODE_ON);
+
assertThat(
camera2Config.getCaptureRequestOption(
CaptureRequest.FLASH_MODE, FLASH_MODE_OFF))
@@ -379,10 +228,9 @@
mSessionConfigArgumentCaptor.capture());
SessionConfig sessionConfig = mSessionConfigArgumentCaptor.getAllValues().get(0);
Camera2Config camera2Config = new Camera2Config(sessionConfig.getImplementationOptions());
- assertThat(
- camera2Config.getCaptureRequestOption(
- CaptureRequest.CONTROL_AE_MODE, CONTROL_AE_MODE_OFF))
- .isEqualTo(CONTROL_AE_MODE_ON_AUTO_FLASH);
+
+ assertAeMode(camera2Config, CONTROL_AE_MODE_ON_AUTO_FLASH);
+
assertThat(camera2Config.getCaptureRequestOption(CaptureRequest.FLASH_MODE, -1))
.isEqualTo(-1);
assertThat(mCamera2CameraControl.isTorchOn()).isFalse();
@@ -392,10 +240,9 @@
CaptureConfig captureConfig = mCaptureConfigArgumentCaptor.getValue().get(0);
Camera2Config resultCaptureConfig =
new Camera2Config(captureConfig.getImplementationOptions());
- assertThat(
- resultCaptureConfig.getCaptureRequestOption(
- CaptureRequest.CONTROL_AE_MODE, null))
- .isEqualTo(CaptureRequest.CONTROL_AE_MODE_ON);
+
+ assertAeMode(resultCaptureConfig, CONTROL_AE_MODE_ON);
+
}
@Test
@@ -522,8 +369,287 @@
Camera2Config sharedOptions =
new Camera2Config(mCamera2CameraControl.getSharedOptions());
- assertThat(resultCaptureConfig.getCaptureRequestOption(CaptureRequest.CONTROL_AF_MODE,
- null)).isEqualTo(
+ assertAfMode(resultCaptureConfig,
sharedOptions.getCaptureRequestOption(CaptureRequest.CONTROL_AF_MODE, null));
}
+
+ @Test
+ public void startFocusAndMetering_3ARegionsUpdatedInSessionAndSharedOptions()
+ throws InterruptedException {
+ SensorOrientedMeteringPointFactory factory = new SensorOrientedMeteringPointFactory(1.0f,
+ 1.0f);
+ FocusMeteringAction action = FocusMeteringAction.Builder.from(factory.createPoint(0, 0))
+ .build();
+ mCamera2CameraControl.startFocusAndMetering(action);
+
+ HandlerUtil.waitForLooperToIdle(mHandler);
+
+ verify(mControlUpdateListener, times(1)).onCameraControlUpdateSessionConfig(
+ mSessionConfigArgumentCaptor.capture());
+ SessionConfig sessionConfig = mSessionConfigArgumentCaptor.getValue();
+ Camera2Config repeatingConfig = new Camera2Config(sessionConfig.getImplementationOptions());
+
+ // Here we verify only 3A region count is correct. Values correctness are left to
+ // FocusMeteringControlTest.
+ assertThat(
+ repeatingConfig.getCaptureRequestOption(
+ CaptureRequest.CONTROL_AF_REGIONS, null)).hasLength(1);
+ assertThat(
+ repeatingConfig.getCaptureRequestOption(
+ CaptureRequest.CONTROL_AE_REGIONS, null)).hasLength(1);
+ assertThat(
+ repeatingConfig.getCaptureRequestOption(
+ CaptureRequest.CONTROL_AWB_REGIONS, null)).hasLength(1);
+
+
+ Camera2Config singleConfig = new Camera2Config(mCamera2CameraControl.getSharedOptions());
+ assertThat(
+ singleConfig.getCaptureRequestOption(
+ CaptureRequest.CONTROL_AF_REGIONS, null)).hasLength(1);
+ assertThat(
+ singleConfig.getCaptureRequestOption(
+ CaptureRequest.CONTROL_AE_REGIONS, null)).hasLength(1);
+ assertThat(
+ singleConfig.getCaptureRequestOption(
+ CaptureRequest.CONTROL_AWB_REGIONS, null)).hasLength(1);
+ }
+
+ @Test
+ public void startFocusAndMetering_AfIsTriggeredProperly() throws InterruptedException {
+ SensorOrientedMeteringPointFactory factory = new SensorOrientedMeteringPointFactory(1.0f,
+ 1.0f);
+ FocusMeteringAction action = FocusMeteringAction.Builder.from(factory.createPoint(0, 0))
+ .build();
+ mCamera2CameraControl.startFocusAndMetering(action);
+ HandlerUtil.waitForLooperToIdle(mHandler);
+
+ verifyAfMode(CaptureRequest.CONTROL_AF_MODE_AUTO);
+
+ verify(mControlUpdateListener).onCameraControlCaptureRequests(
+ mCaptureConfigArgumentCaptor.capture());
+
+ CaptureConfig captureConfig = mCaptureConfigArgumentCaptor.getValue().get(0);
+ Camera2Config resultCaptureConfig =
+ new Camera2Config(captureConfig.getImplementationOptions());
+
+ // Trigger AF
+ assertThat(resultCaptureConfig.getCaptureRequestOption(CaptureRequest.CONTROL_AF_TRIGGER,
+ null)).isEqualTo(CaptureRequest.CONTROL_AF_TRIGGER_START);
+ }
+
+ @Test
+ public void startFocusAndMetering_AFNotInvolved_AfIsNotTriggered() throws InterruptedException {
+ SensorOrientedMeteringPointFactory factory = new SensorOrientedMeteringPointFactory(1.0f,
+ 1.0f);
+ FocusMeteringAction action = FocusMeteringAction.Builder.from(factory.createPoint(0, 0),
+ MeteringMode.AE_AWB)
+ .build();
+ mCamera2CameraControl.startFocusAndMetering(action);
+ HandlerUtil.waitForLooperToIdle(mHandler);
+
+ verifyAfMode(CaptureRequest.CONTROL_AF_MODE_CONTINUOUS_PICTURE);
+
+ verify(mControlUpdateListener, never()).onCameraControlCaptureRequests(any());
+ }
+
+ @Test
+ public void cancelFocusAndMetering_3ARegionsReset() throws InterruptedException {
+ SensorOrientedMeteringPointFactory factory = new SensorOrientedMeteringPointFactory(1.0f,
+ 1.0f);
+ FocusMeteringAction action = FocusMeteringAction.Builder.from(factory.createPoint(0, 0))
+ .build();
+ mCamera2CameraControl.startFocusAndMetering(action);
+ HandlerUtil.waitForLooperToIdle(mHandler);
+ Mockito.reset(mControlUpdateListener);
+
+ mCamera2CameraControl.cancelFocusAndMetering();
+ HandlerUtil.waitForLooperToIdle(mHandler);
+
+ verify(mControlUpdateListener, times(1)).onCameraControlUpdateSessionConfig(
+ mSessionConfigArgumentCaptor.capture());
+ SessionConfig sessionConfig = mSessionConfigArgumentCaptor.getValue();
+ Camera2Config repeatingConfig = new Camera2Config(sessionConfig.getImplementationOptions());
+
+ assertThat(
+ repeatingConfig.getCaptureRequestOption(
+ CaptureRequest.CONTROL_AF_REGIONS, null)).isNull();
+ assertThat(
+ repeatingConfig.getCaptureRequestOption(
+ CaptureRequest.CONTROL_AE_REGIONS, null)).isNull();
+ assertThat(
+ repeatingConfig.getCaptureRequestOption(
+ CaptureRequest.CONTROL_AWB_REGIONS, null)).isNull();
+
+
+ Camera2Config singleConfig = new Camera2Config(mCamera2CameraControl.getSharedOptions());
+ assertThat(
+ singleConfig.getCaptureRequestOption(
+ CaptureRequest.CONTROL_AF_REGIONS, null)).isNull();
+ assertThat(
+ singleConfig.getCaptureRequestOption(
+ CaptureRequest.CONTROL_AE_REGIONS, null)).isNull();
+ assertThat(
+ singleConfig.getCaptureRequestOption(
+ CaptureRequest.CONTROL_AWB_REGIONS, null)).isNull();
+ }
+
+ @Test
+ public void cancelFocusAndMetering_cancelAfProperly() throws InterruptedException {
+ SensorOrientedMeteringPointFactory factory = new SensorOrientedMeteringPointFactory(1.0f,
+ 1.0f);
+ FocusMeteringAction action = FocusMeteringAction.Builder.from(factory.createPoint(0, 0))
+ .build();
+ mCamera2CameraControl.startFocusAndMetering(action);
+ HandlerUtil.waitForLooperToIdle(mHandler);
+ Mockito.reset(mControlUpdateListener);
+ mCamera2CameraControl.cancelFocusAndMetering();
+ HandlerUtil.waitForLooperToIdle(mHandler);
+
+ verifyAfMode(CaptureRequest.CONTROL_AF_MODE_CONTINUOUS_PICTURE);
+
+ verify(mControlUpdateListener).onCameraControlCaptureRequests(
+ mCaptureConfigArgumentCaptor.capture());
+
+ CaptureConfig captureConfig = mCaptureConfigArgumentCaptor.getValue().get(0);
+ Camera2Config resultCaptureConfig =
+ new Camera2Config(captureConfig.getImplementationOptions());
+
+ // Trigger AF
+ assertThat(resultCaptureConfig.getCaptureRequestOption(CaptureRequest.CONTROL_AF_TRIGGER,
+ null)).isEqualTo(CaptureRequest.CONTROL_AF_TRIGGER_CANCEL);
+ }
+
+ private void verifyAfMode(int expectAfMode) {
+ verify(mControlUpdateListener, times(1)).onCameraControlUpdateSessionConfig(
+ mSessionConfigArgumentCaptor.capture());
+ SessionConfig sessionConfig = mSessionConfigArgumentCaptor.getValue();
+ Camera2Config repeatingConfig = new Camera2Config(sessionConfig.getImplementationOptions());
+ assertThat(repeatingConfig.getCaptureRequestOption(CaptureRequest.CONTROL_AF_MODE,
+ null)).isEqualTo(expectAfMode);
+ }
+
+ @Test
+ public void cancelFocusAndMetering_AFNotInvolved_notCancelAF() throws InterruptedException {
+ SensorOrientedMeteringPointFactory factory = new SensorOrientedMeteringPointFactory(1.0f,
+ 1.0f);
+ FocusMeteringAction action = FocusMeteringAction.Builder.from(factory.createPoint(0, 0),
+ MeteringMode.AE_ONLY)
+ .build();
+ mCamera2CameraControl.startFocusAndMetering(action);
+ HandlerUtil.waitForLooperToIdle(mHandler);
+ Mockito.reset(mControlUpdateListener);
+ mCamera2CameraControl.cancelFocusAndMetering();
+ HandlerUtil.waitForLooperToIdle(mHandler);
+
+ verify(mControlUpdateListener, never()).onCameraControlCaptureRequests(any());
+
+ verifyAfMode(CaptureRequest.CONTROL_AF_MODE_CONTINUOUS_PICTURE);
+ }
+
+ @Test
+ public void startFocus_afModeIsSetToAuto() throws InterruptedException {
+ SensorOrientedMeteringPointFactory factory = new SensorOrientedMeteringPointFactory(1.0f,
+ 1.0f);
+ FocusMeteringAction action = FocusMeteringAction.Builder.from(factory.createPoint(0, 0))
+ .build();
+ mCamera2CameraControl.startFocusAndMetering(action);
+ HandlerUtil.waitForLooperToIdle(mHandler);
+
+ Camera2Config singleConfig = new Camera2Config(mCamera2CameraControl.getSharedOptions());
+
+ mCamera2CameraControl.cancelFocusAndMetering();
+ HandlerUtil.waitForLooperToIdle(mHandler);
+
+ Camera2Config singleConfig2 = new Camera2Config(mCamera2CameraControl.getSharedOptions());
+
+ assertThat(
+ singleConfig.getCaptureRequestOption(
+ CaptureRequest.CONTROL_AF_MODE, null))
+ .isEqualTo(CaptureRequest.CONTROL_AF_MODE_AUTO);
+ assertThat(
+ singleConfig2.getCaptureRequestOption(
+ CaptureRequest.CONTROL_AF_MODE, null))
+ .isEqualTo(CaptureRequest.CONTROL_AF_MODE_CONTINUOUS_PICTURE);
+ }
+
+ private boolean isAfModeSupported(int afMode) {
+ int[] modes = mCameraCharacteristics.get(CameraCharacteristics.CONTROL_AF_AVAILABLE_MODES);
+ return isModeInList(afMode, modes);
+ }
+
+ private boolean isAeModeSupported(int aeMode) {
+ int[] modes = mCameraCharacteristics.get(CameraCharacteristics.CONTROL_AE_AVAILABLE_MODES);
+ return isModeInList(aeMode, modes);
+ }
+
+ private boolean isAwbModeSupported(int awbMode) {
+ int[] modes = mCameraCharacteristics.get(CameraCharacteristics.CONTROL_AWB_AVAILABLE_MODES);
+ return isModeInList(awbMode, modes);
+ }
+
+
+ private boolean isModeInList(int mode, int[] modeList) {
+ if (modeList == null) {
+ return false;
+ }
+ for (int m : modeList) {
+ if (mode == m) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ private void assertAfMode(Camera2Config config, int afMode) {
+ if (isAfModeSupported(afMode)) {
+ assertThat(config.getCaptureRequestOption(
+ CaptureRequest.CONTROL_AF_MODE, null)).isEqualTo(afMode);
+ } else {
+ int fallbackMode;
+ if (isAfModeSupported(CONTROL_AF_MODE_CONTINUOUS_PICTURE)) {
+ fallbackMode = CONTROL_AF_MODE_CONTINUOUS_PICTURE;
+ } else if (isAfModeSupported(CONTROL_AF_MODE_AUTO)) {
+ fallbackMode = CONTROL_AF_MODE_AUTO;
+ } else {
+ fallbackMode = CONTROL_AF_MODE_OFF;
+ }
+
+ assertThat(config.getCaptureRequestOption(
+ CaptureRequest.CONTROL_AF_MODE, null)).isEqualTo(fallbackMode);
+ }
+ }
+
+ private void assertAeMode(Camera2Config config, int aeMode) {
+ if (isAeModeSupported(aeMode)) {
+ assertThat(config.getCaptureRequestOption(
+ CaptureRequest.CONTROL_AE_MODE, null)).isEqualTo(aeMode);
+ } else {
+ int fallbackMode;
+ if (isAeModeSupported(CONTROL_AE_MODE_ON)) {
+ fallbackMode = CONTROL_AE_MODE_ON;
+ } else {
+ fallbackMode = CONTROL_AE_MODE_OFF;
+ }
+
+ assertThat(config.getCaptureRequestOption(
+ CaptureRequest.CONTROL_AE_MODE, null)).isEqualTo(fallbackMode);
+ }
+ }
+
+ private void assertAwbMode(Camera2Config config, int awbMode) {
+ if (isAwbModeSupported(awbMode)) {
+ assertThat(config.getCaptureRequestOption(
+ CaptureRequest.CONTROL_AWB_MODE, null)).isEqualTo(awbMode);
+ } else {
+ int fallbackMode;
+ if (isAwbModeSupported(CONTROL_AWB_MODE_AUTO)) {
+ fallbackMode = CONTROL_AWB_MODE_AUTO;
+ } else {
+ fallbackMode = CONTROL_AWB_MODE_OFF;
+ }
+
+ assertThat(config.getCaptureRequestOption(
+ CaptureRequest.CONTROL_AWB_MODE, null)).isEqualTo(fallbackMode);
+ }
+ }
}
diff --git a/camera/camera-camera2/src/androidTest/java/androidx/camera/camera2/impl/Camera2InitializerTest.java b/camera/camera-camera2/src/androidTest/java/androidx/camera/camera2/impl/Camera2InitializerTest.java
deleted file mode 100644
index 15d8636..0000000
--- a/camera/camera-camera2/src/androidTest/java/androidx/camera/camera2/impl/Camera2InitializerTest.java
+++ /dev/null
@@ -1,60 +0,0 @@
-/*
- * 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.camera.camera2.impl;
-
-import static com.google.common.truth.Truth.assertThat;
-
-import android.content.Context;
-import android.content.Intent;
-
-import androidx.camera.testing.fakes.FakeActivity;
-import androidx.test.core.app.ApplicationProvider;
-import androidx.test.ext.junit.runners.AndroidJUnit4;
-import androidx.test.filters.LargeTest;
-import androidx.test.rule.ActivityTestRule;
-
-import org.junit.Before;
-import org.junit.Rule;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-/**
- * Unit tests for {@link Camera2Initializer}.
- */
-@LargeTest
-@RunWith(AndroidJUnit4.class)
-public final class Camera2InitializerTest {
-
- @Rule
- public ActivityTestRule<FakeActivity> activityRule =
- new ActivityTestRule<>(
- FakeActivity.class, /*initialTouchMode=*/ false, /*launchActivity=*/ false);
- private Context mAppContext;
-
- @Before
- public void setUp() {
- mAppContext = ApplicationProvider.getApplicationContext();
- }
-
- @Test
- public void cameraXIsInitialized_beforeActivityIsCreated() {
- activityRule.launchActivity(new Intent(mAppContext, FakeActivity.class));
- FakeActivity activity = activityRule.getActivity();
-
- assertThat(activity.isCameraXInitializedAtOnCreate()).isTrue();
- }
-}
diff --git a/camera/camera-camera2/src/androidTest/java/androidx/camera/camera2/impl/CaptureSessionTest.java b/camera/camera-camera2/src/androidTest/java/androidx/camera/camera2/impl/CaptureSessionTest.java
index 507177c..1c95496 100644
--- a/camera/camera-camera2/src/androidTest/java/androidx/camera/camera2/impl/CaptureSessionTest.java
+++ b/camera/camera-camera2/src/androidTest/java/androidx/camera/camera2/impl/CaptureSessionTest.java
@@ -52,6 +52,8 @@
import androidx.camera.core.ImmediateSurface;
import androidx.camera.core.MutableOptionsBundle;
import androidx.camera.core.SessionConfig;
+import androidx.camera.core.impl.utils.executor.CameraXExecutors;
+import androidx.camera.core.impl.utils.futures.Futures;
import androidx.camera.testing.CameraUtil;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.LargeTest;
@@ -69,10 +71,13 @@
import org.mockito.InOrder;
import org.mockito.Mockito;
+import java.util.ArrayList;
import java.util.Collections;
+import java.util.List;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Executor;
+import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
/**
@@ -88,6 +93,8 @@
private CameraDevice mCameraDevice;
+ private final List<CaptureSession> mCaptureSessions = new ArrayList<>();
+
@Rule
public GrantPermissionRule mRuntimePermissionRule = GrantPermissionRule.grant(
Manifest.permission.CAMERA);
@@ -102,7 +109,16 @@
}
@After
- public void tearDown() {
+ public void tearDown() throws ExecutionException, InterruptedException {
+ // Ensure all capture sessions are fully closed
+ List<ListenableFuture<Void>> releaseFutures = new ArrayList<>();
+ for (CaptureSession captureSession : mCaptureSessions) {
+ releaseFutures.add(captureSession.release(/*abortInFlightCaptures=*/false));
+ }
+ mCaptureSessions.clear();
+ Future<?> aggregateReleaseFuture = Futures.allAsList(releaseFutures);
+ aggregateReleaseFuture.get();
+
if (mCameraDevice != null) {
mTestParameters0.tearDown();
mTestParameters1.tearDown();
@@ -112,7 +128,7 @@
@Test
public void setCaptureSessionSucceed() {
- CaptureSession captureSession = new CaptureSession(mTestParameters0.mHandler);
+ CaptureSession captureSession = createCaptureSession(mTestParameters0);
captureSession.setSessionConfig(mTestParameters0.mSessionConfig);
@@ -121,7 +137,7 @@
@Test(expected = IllegalStateException.class)
public void setCaptureSessionOnClosedSession_throwsException() {
- CaptureSession captureSession = new CaptureSession(mTestParameters0.mHandler);
+ CaptureSession captureSession = createCaptureSession(mTestParameters0);
SessionConfig newSessionConfig = mTestParameters0.mSessionConfig;
captureSession.close();
@@ -132,7 +148,7 @@
@Test
public void openCaptureSessionSucceed() throws CameraAccessException, InterruptedException {
- CaptureSession captureSession = new CaptureSession(mTestParameters0.mHandler);
+ CaptureSession captureSession = createCaptureSession(mTestParameters0);
captureSession.setSessionConfig(mTestParameters0.mSessionConfig);
captureSession.open(mTestParameters0.mSessionConfig, mCameraDevice);
@@ -153,7 +169,7 @@
@Test
public void openCaptureSessionWithOptionOverride()
throws CameraAccessException, InterruptedException {
- CaptureSession captureSession = new CaptureSession(mTestParameters0.mHandler);
+ CaptureSession captureSession = createCaptureSession(mTestParameters0);
captureSession.setSessionConfig(mTestParameters0.mSessionConfig);
captureSession.open(mTestParameters0.mSessionConfig, mCameraDevice);
@@ -192,7 +208,7 @@
@Test
public void closeUnopenedSession() {
- CaptureSession captureSession = new CaptureSession(mTestParameters0.mHandler);
+ CaptureSession captureSession = createCaptureSession(mTestParameters0);
captureSession.setSessionConfig(mTestParameters0.mSessionConfig);
captureSession.close();
@@ -201,23 +217,18 @@
}
@Test
- public void releaseUnopenedSession() throws ExecutionException, InterruptedException {
- CaptureSession captureSession = new CaptureSession(mTestParameters0.mHandler);
+ public void releaseUnopenedSession() {
+ CaptureSession captureSession = createCaptureSession(mTestParameters0);
captureSession.setSessionConfig(mTestParameters0.mSessionConfig);
- ListenableFuture<Void> releaseFuture = captureSession.release(
- /*abortInFlightCaptures=*/false);
-
- // Wait for release
- releaseFuture.get();
+ captureSession.release(/*abortInFlightCaptures=*/false);
assertThat(captureSession.getState()).isEqualTo(State.RELEASED);
}
@Test
- public void closeOpenedSession()
- throws CameraAccessException, InterruptedException, ExecutionException {
- CaptureSession captureSession = new CaptureSession(mTestParameters0.mHandler);
+ public void closeOpenedSession() throws CameraAccessException {
+ CaptureSession captureSession = createCaptureSession(mTestParameters0);
captureSession.setSessionConfig(mTestParameters0.mSessionConfig);
captureSession.open(mTestParameters0.mSessionConfig, mCameraDevice);
@@ -226,19 +237,12 @@
// Session should be in closed state immediately after calling close() on an
// opening/opened session.
assertThat(captureSession.getState()).isEqualTo(State.CLOSED);
-
- // Release the session to clean up for next test
- ListenableFuture<Void> releaseFuture = captureSession.release(
- /*abortInFlightCaptures=*/false);
-
- // Wait for release to finish
- releaseFuture.get();
}
@Test
public void releaseOpenedSession()
throws CameraAccessException, InterruptedException, ExecutionException {
- CaptureSession captureSession = new CaptureSession(mTestParameters0.mHandler);
+ CaptureSession captureSession = createCaptureSession(mTestParameters0);
captureSession.setSessionConfig(mTestParameters0.mSessionConfig);
captureSession.open(mTestParameters0.mSessionConfig, mCameraDevice);
ListenableFuture<Void> releaseFuture = captureSession.release(
@@ -255,22 +259,22 @@
@Test
public void openSecondSession() throws CameraAccessException, InterruptedException {
- CaptureSession captureSession = new CaptureSession(mTestParameters0.mHandler);
- captureSession.setSessionConfig(mTestParameters0.mSessionConfig);
+ CaptureSession captureSession0 = createCaptureSession(mTestParameters0);
+ captureSession0.setSessionConfig(mTestParameters0.mSessionConfig);
// First session is opened
- captureSession.open(mTestParameters0.mSessionConfig, mCameraDevice);
- captureSession.close();
+ captureSession0.open(mTestParameters0.mSessionConfig, mCameraDevice);
+ captureSession0.close();
// Open second session, which should cause first one to be released
- CaptureSession captureSession1 = new CaptureSession(mTestParameters1.mHandler);
+ CaptureSession captureSession1 = createCaptureSession(mTestParameters1);
captureSession1.setSessionConfig(mTestParameters1.mSessionConfig);
captureSession1.open(mTestParameters1.mSessionConfig, mCameraDevice);
mTestParameters1.waitForData();
assertThat(captureSession1.getState()).isEqualTo(State.OPENED);
- assertThat(captureSession.getState()).isEqualTo(State.RELEASED);
+ assertThat(captureSession0.getState()).isEqualTo(State.RELEASED);
// First session should have StateCallback.onConfigured(), onClosed() calls.
verify(mTestParameters0.mSessionStateCallback, times(1))
@@ -289,7 +293,7 @@
@Test
public void issueCaptureRequest() throws CameraAccessException, InterruptedException {
- CaptureSession captureSession = new CaptureSession(mTestParameters0.mHandler);
+ CaptureSession captureSession = createCaptureSession(mTestParameters0);
captureSession.setSessionConfig(mTestParameters0.mSessionConfig);
captureSession.open(mTestParameters0.mSessionConfig, mCameraDevice);
@@ -310,7 +314,7 @@
@Test
public void issueCaptureRequestAppendAndOverrideRepeatingOptions()
throws CameraAccessException, InterruptedException {
- CaptureSession captureSession = new CaptureSession(mTestParameters0.mHandler);
+ CaptureSession captureSession = createCaptureSession(mTestParameters0);
captureSession.setSessionConfig(mTestParameters0.mSessionConfig);
captureSession.open(mTestParameters0.mSessionConfig, mCameraDevice);
@@ -348,23 +352,24 @@
assertThat(captureResult.getRequest().get(CaptureRequest.CONTROL_AE_MODE)).isEqualTo(
CaptureRequest.CONTROL_AE_MODE_ON);
}
+
@Test
public void issueCaptureRequestAcrossCaptureSessions()
throws CameraAccessException, InterruptedException {
- CaptureSession captureSession = new CaptureSession(mTestParameters0.mHandler);
- captureSession.setSessionConfig(mTestParameters0.mSessionConfig);
+ CaptureSession captureSession0 = createCaptureSession(mTestParameters0);
+ captureSession0.setSessionConfig(mTestParameters0.mSessionConfig);
- captureSession.issueCaptureRequests(
+ captureSession0.issueCaptureRequests(
Collections.singletonList(mTestParameters0.mCaptureConfig));
- captureSession.open(mTestParameters0.mSessionConfig, mCameraDevice);
+ captureSession0.open(mTestParameters0.mSessionConfig, mCameraDevice);
- captureSession.close();
- CaptureSession captureSession2 = new CaptureSession(mTestParameters0.mHandler);
- captureSession2.setSessionConfig(captureSession.getSessionConfig());
- if (!captureSession.getCaptureConfigs().isEmpty()) {
- captureSession2.issueCaptureRequests(captureSession.getCaptureConfigs());
+ captureSession0.close();
+ CaptureSession captureSession1 = createCaptureSession(mTestParameters0);
+ captureSession1.setSessionConfig(captureSession0.getSessionConfig());
+ if (!captureSession0.getCaptureConfigs().isEmpty()) {
+ captureSession1.issueCaptureRequests(captureSession0.getCaptureConfigs());
}
- captureSession2.open(mTestParameters0.mSessionConfig, mCameraDevice);
+ captureSession1.open(mTestParameters0.mSessionConfig, mCameraDevice);
mTestParameters0.waitForCameraCaptureCallback();
@@ -376,7 +381,7 @@
@Test
public void issueCaptureRequestBeforeCaptureSessionOpened()
throws CameraAccessException, InterruptedException {
- CaptureSession captureSession = new CaptureSession(mTestParameters0.mHandler);
+ CaptureSession captureSession = createCaptureSession(mTestParameters0);
captureSession.setSessionConfig(mTestParameters0.mSessionConfig);
captureSession.issueCaptureRequests(
@@ -392,7 +397,7 @@
@Test(expected = IllegalStateException.class)
public void issueCaptureRequestOnClosedSession_throwsException() {
- CaptureSession captureSession = new CaptureSession(mTestParameters0.mHandler);
+ CaptureSession captureSession = createCaptureSession(mTestParameters0);
captureSession.close();
@@ -404,7 +409,7 @@
@Test
public void surfaceOnDetachedListenerIsCalledWhenSessionIsClose()
throws CameraAccessException, InterruptedException, ExecutionException {
- CaptureSession captureSession = new CaptureSession(mTestParameters0.mHandler);
+ CaptureSession captureSession = createCaptureSession(mTestParameters0);
captureSession.setSessionConfig(mTestParameters0.mSessionConfig);
captureSession.open(mTestParameters0.mSessionConfig, mCameraDevice);
@@ -432,7 +437,7 @@
@Test
public void cameraEventCallbackInvokedInOrder() throws CameraAccessException {
- CaptureSession captureSession = new CaptureSession(mTestParameters0.mHandler);
+ CaptureSession captureSession = createCaptureSession(mTestParameters0);
captureSession.setSessionConfig(mTestParameters0.mSessionConfig);
captureSession.open(mTestParameters0.mSessionConfig, mCameraDevice);
@@ -457,7 +462,7 @@
ArgumentCaptor<CameraCaptureResult> captureResultCaptor = ArgumentCaptor.forClass(
CameraCaptureResult.class);
- CaptureSession captureSession = new CaptureSession(mTestParameters0.mHandler);
+ CaptureSession captureSession = createCaptureSession(mTestParameters0);
captureSession.setSessionConfig(mTestParameters0.mSessionConfig);
// Open the capture session and verify the onEnableSession callback would be invoked
@@ -496,6 +501,12 @@
never()).onCaptureCompleted(any(CameraCaptureResult.class));
}
+ private CaptureSession createCaptureSession(CaptureSessionTestParameters testParams) {
+ CaptureSession captureSession = new CaptureSession(testParams.mExecutor);
+ mCaptureSessions.add(captureSession);
+ return captureSession;
+ }
+
/**
* A implementation to test {@link CameraEventCallback} on CaptureSession.
*/
@@ -531,7 +542,7 @@
}
}
- private static CaptureConfig getCaptureConfig(CaptureRequest.Key key, int effectValue,
+ private static <T> CaptureConfig getCaptureConfig(CaptureRequest.Key<T> key, T effectValue,
CameraCaptureCallback callback) {
CaptureConfig.Builder captureConfigBuilder = new CaptureConfig.Builder();
Camera2Config.Builder camera2ConfigurationBuilder =
@@ -552,6 +563,8 @@
private final HandlerThread mHandlerThread;
/** Handler for all asynchronous calls. */
private final Handler mHandler;
+ /** Executor which delegates to Handler */
+ private final Executor mExecutor;
/** Latch to wait for first image data to appear. */
private final CountDownLatch mDataLatch = new CountDownLatch(1);
@@ -608,6 +621,8 @@
mHandlerThread.start();
mHandler = new Handler(mHandlerThread.getLooper());
+ mExecutor = CameraXExecutors.newHandlerExecutor(mHandler);
+
mImageReader =
ImageReader.newInstance(640, 480, ImageFormat.YUV_420_888, /*maxImages*/ 2);
mImageReader.setOnImageAvailableListener(mOnImageAvailableListener, mHandler);
diff --git a/camera/camera-camera2/src/main/java/androidx/camera/camera2/impl/Camera.java b/camera/camera-camera2/src/main/java/androidx/camera/camera2/impl/Camera.java
index 2577688..6189a209 100644
--- a/camera/camera-camera2/src/main/java/androidx/camera/camera2/impl/Camera.java
+++ b/camera/camera-camera2/src/main/java/androidx/camera/camera2/impl/Camera.java
@@ -26,6 +26,8 @@
import android.os.Handler;
import android.os.Looper;
import android.util.Log;
+import android.util.Rational;
+import android.util.Size;
import android.view.Surface;
import androidx.annotation.GuardedBy;
@@ -44,6 +46,7 @@
import androidx.camera.core.DeferrableSurface;
import androidx.camera.core.ImmediateSurface;
import androidx.camera.core.Observable;
+import androidx.camera.core.Preview;
import androidx.camera.core.SessionConfig;
import androidx.camera.core.SessionConfig.ValidatingBuilder;
import androidx.camera.core.UseCase;
@@ -63,6 +66,7 @@
import java.util.List;
import java.util.Locale;
import java.util.Map;
+import java.util.concurrent.Executor;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.atomic.AtomicInteger;
@@ -95,8 +99,11 @@
private final Object mCameraInfoLock = new Object();
/** The handler for camera callbacks and use case state management calls. */
+
@SuppressWarnings("WeakerAccess") /* synthetic accessor */
final Handler mHandler;
+ private final Executor mExecutor;
+
/**
* State variable for tracking state of the camera.
*
@@ -108,7 +115,7 @@
private final LiveDataObservable<BaseCamera.State> mObservableState =
new LiveDataObservable<>();
/** The camera control shared across all use cases bound to this Camera. */
- private final CameraControlInternal mCameraControlInternal;
+ private final Camera2CameraControl mCameraControlInternal;
private final StateCallback mStateCallback = new StateCallback();
/** Information about the characteristics of this camera */
// Nullable because this is lazily instantiated
@@ -122,7 +129,7 @@
@SuppressWarnings("WeakerAccess") /* synthetic accessor */
int mCameraDeviceError = ERROR_NONE;
/** The configured session which handles issuing capture requests. */
- private CaptureSession mCaptureSession = new CaptureSession(null);
+ private CaptureSession mCaptureSession;
/** The session configuration of camera control. */
private SessionConfig mCameraControlSessionConfig = SessionConfig.defaultEmptySessionConfig();
@@ -154,7 +161,6 @@
@SuppressWarnings("WeakerAccess")
int mNumAvailableCameras = 0;
-
/**
* Constructor for a camera.
*
@@ -172,14 +178,23 @@
mAvailableCamerasObservable = availableCamerasObservable;
mHandler = handler;
ScheduledExecutorService executorScheduler = CameraXExecutors.newHandlerExecutor(mHandler);
+ mExecutor = executorScheduler;
mUseCaseAttachState = new UseCaseAttachState(cameraId);
mObservableState.postValue(State.CLOSED);
- mCameraControlInternal = new Camera2CameraControl(this, executorScheduler,
- executorScheduler);
- mCaptureSession = new CaptureSession(mHandler);
+
+ try {
+ CameraCharacteristics cameraCharacteristics =
+ mCameraManager.getCameraCharacteristics(mCameraId);
+ mCameraControlInternal = new Camera2CameraControl(cameraCharacteristics,
+ this, executorScheduler, executorScheduler);
+ } catch (CameraAccessException e) {
+ throw new IllegalStateException("Cannot access camera", e);
+ }
+
+ mCaptureSession = new CaptureSession(mExecutor);
// Register an observer to update the number of available cameras
- mAvailableCamerasObservable.addObserver(executorScheduler, mAvailableCamerasObserver);
+ mAvailableCamerasObservable.addObserver(mExecutor, mAvailableCamerasObserver);
}
/**
@@ -266,7 +281,7 @@
private void configAndClose() {
// Configure the camera with a dummy capture session in order to clear the
// previous session. This should be released immediately after being configured.
- final CaptureSession dummySession = new CaptureSession(null);
+ final CaptureSession dummySession = new CaptureSession(mExecutor);
final SurfaceTexture surfaceTexture = new SurfaceTexture(0);
surfaceTexture.setDefaultBufferSize(640, 480);
@@ -604,7 +619,7 @@
// Re-attaches use case's surfaces if surfaces are changed when use case is online.
@GuardedBy("mAttachedUseCaseLock")
private void reattachUseCaseSurfaces(UseCase useCase) {
- // if use case is offline, then DeferrableSurface attaching will happens when the use
+ // if use case is offline, then DeferrableSurface attaching will happen when the use
// case is addOnlineUsecase()'d. So here we don't need to do the attaching.
if (!isUseCaseOnline(useCase)) {
return;
@@ -707,7 +722,28 @@
open();
}
+ updateCameraControlPreviewAspectRatio(useCases);
+ }
+
+ private void updateCameraControlPreviewAspectRatio(Collection<UseCase> useCases) {
+ for (UseCase useCase : useCases) {
+ if (useCase instanceof Preview) {
+ Size resolutoin = useCase.getAttachedSurfaceResolution(mCameraId);
+ Rational aspectRatio = new Rational(resolutoin.getWidth(), resolutoin.getHeight());
+ mCameraControlInternal.setPreviewAspectRatio(aspectRatio);
+ return;
+ }
+ }
+ }
+
+ private void clearCameraControlPreviewAspectRatio(Collection<UseCase> useCases) {
+ for (UseCase useCase : useCases) {
+ if (useCase instanceof Preview) {
+ mCameraControlInternal.setPreviewAspectRatio(null);
+ return;
+ }
+ }
}
/**
@@ -758,6 +794,7 @@
openCaptureSession();
}
+ clearCameraControlPreviewAspectRatio(useCases);
}
/** Returns an interface to retrieve characteristics of the camera. */
@@ -857,7 +894,7 @@
// Recreate an initialized (but not opened) capture session from the previous configuration
SessionConfig previousSessionConfig = oldCaptureSession.getSessionConfig();
List<CaptureConfig> unissuedCaptureConfigs = oldCaptureSession.getCaptureConfigs();
- mCaptureSession = new CaptureSession(mHandler);
+ mCaptureSession = new CaptureSession(mExecutor);
mCaptureSession.setSessionConfig(previousSessionConfig);
mCaptureSession.issueCaptureRequests(unissuedCaptureConfigs);
@@ -1167,8 +1204,9 @@
throw new IllegalStateException(
"onDisconnected() should not be possible from state: " + mState);
}
-
- closeCamera(/*abortInFlightCaptures=*/true);
+ // Not to close the in flight captures since the capture session has already been
+ // closed.
+ closeCamera(/*abortInFlightCaptures=*/false);
}
@Override
diff --git a/camera/camera-camera2/src/main/java/androidx/camera/camera2/impl/Camera2CameraControl.java b/camera/camera-camera2/src/main/java/androidx/camera/camera2/impl/Camera2CameraControl.java
index da2118e..de8004e 100644
--- a/camera/camera-camera2/src/main/java/androidx/camera/camera2/impl/Camera2CameraControl.java
+++ b/camera/camera-camera2/src/main/java/androidx/camera/camera2/impl/Camera2CameraControl.java
@@ -16,17 +16,14 @@
package androidx.camera.camera2.impl;
-import android.annotation.SuppressLint;
import android.graphics.Rect;
import android.hardware.camera2.CameraCaptureSession;
import android.hardware.camera2.CameraCaptureSession.CaptureCallback;
+import android.hardware.camera2.CameraCharacteristics;
import android.hardware.camera2.CameraDevice;
import android.hardware.camera2.CaptureRequest;
-import android.hardware.camera2.CaptureResult;
import android.hardware.camera2.TotalCaptureResult;
-import android.hardware.camera2.params.MeteringRectangle;
-import android.os.Build;
-import android.util.Log;
+import android.util.Rational;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
@@ -38,7 +35,7 @@
import androidx.camera.core.CaptureConfig;
import androidx.camera.core.Config;
import androidx.camera.core.FlashMode;
-import androidx.camera.core.OnFocusListener;
+import androidx.camera.core.FocusMeteringAction;
import androidx.camera.core.SessionConfig;
import androidx.camera.core.impl.utils.executor.CameraXExecutors;
@@ -49,8 +46,6 @@
import java.util.Set;
import java.util.concurrent.Executor;
import java.util.concurrent.ScheduledExecutorService;
-import java.util.concurrent.ScheduledFuture;
-import java.util.concurrent.TimeUnit;
/**
* A Camera2 implementation for CameraControlInternal interface
@@ -59,50 +54,32 @@
*/
@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
public final class Camera2CameraControl implements CameraControlInternal {
- private static final long DEFAULT_FOCUS_TIMEOUT_MS = 5000;
private static final String TAG = "Camera2CameraControl";
@SuppressWarnings("WeakerAccess") /* synthetic accessor */
final CameraControlSessionCallback mSessionCallback;
@SuppressWarnings("WeakerAccess") /* synthetic accessor */
final Executor mExecutor;
+ private final CameraCharacteristics mCameraCharacteristics;
private final ControlUpdateListener mControlUpdateListener;
private final ScheduledExecutorService mScheduler;
private final SessionConfig.Builder mSessionConfigBuilder = new SessionConfig.Builder();
-
+ @SuppressWarnings("WeakerAccess") /* synthetic accessor */
+ volatile Rational mPreviewAspectRatio = null;
+ @VisibleForTesting
+ final FocusMeteringControl mFocusMeteringControl;
// use volatile modifier to make these variables in sync in all threads.
private volatile boolean mIsTorchOn = false;
- private volatile boolean mIsFocusLocked = false;
private volatile FlashMode mFlashMode = FlashMode.OFF;
//******************** Should only be accessed by executor *****************************//
- @SuppressWarnings("WeakerAccess") /* synthetic accessor */
- CaptureResultListener mSessionListenerForFocus = null;
private Rect mCropRect = null;
- @SuppressWarnings("WeakerAccess") /* synthetic accessor */
- MeteringRectangle mAfRect;
- @SuppressWarnings("WeakerAccess") /* synthetic accessor */
- MeteringRectangle mAeRect;
- @SuppressWarnings("WeakerAccess") /* synthetic accessor */
- MeteringRectangle mAwbRect;
- @SuppressWarnings("WeakerAccess") /* synthetic accessor */
- Integer mCurrentAfState = CaptureResult.CONTROL_AF_STATE_INACTIVE;
- @SuppressWarnings("WeakerAccess") /* synthetic accessor */
- long mFocusTimeoutCounter = 0;
- private long mFocusTimeoutMs;
- private ScheduledFuture<?> mFocusTimeoutHandle;
//**************************************************************************************//
- public Camera2CameraControl(@NonNull ControlUpdateListener controlUpdateListener,
- @NonNull ScheduledExecutorService scheduler, @NonNull Executor executor) {
- this(controlUpdateListener, DEFAULT_FOCUS_TIMEOUT_MS, scheduler, executor);
- }
-
- public Camera2CameraControl(
+ public Camera2CameraControl(@NonNull CameraCharacteristics cameraCharacteristics,
@NonNull ControlUpdateListener controlUpdateListener,
- long focusTimeoutMs,
- @NonNull ScheduledExecutorService scheduler,
- @NonNull Executor executor) {
+ @NonNull ScheduledExecutorService scheduler, @NonNull Executor executor) {
+ mCameraCharacteristics = cameraCharacteristics;
mControlUpdateListener = controlUpdateListener;
if (CameraXExecutors.isSequentialExecutor(executor)) {
mExecutor = executor;
@@ -110,14 +87,13 @@
mExecutor = CameraXExecutors.newSequentialExecutor(executor);
}
mScheduler = scheduler;
- mFocusTimeoutMs = focusTimeoutMs;
-
mSessionCallback = new CameraControlSessionCallback(mExecutor);
-
mSessionConfigBuilder.setTemplateType(getDefaultTemplate());
mSessionConfigBuilder.addRepeatingCameraCaptureCallback(
CaptureCallbackContainer.create(mSessionCallback));
+ mFocusMeteringControl = new FocusMeteringControl(this, mExecutor, mScheduler);
+
// Initialize the session config
mExecutor.execute(new Runnable() {
@Override
@@ -127,6 +103,30 @@
});
}
+ public void setPreviewAspectRatio(@Nullable Rational previewAspectRatio) {
+ mPreviewAspectRatio = previewAspectRatio;
+ }
+
+ @Override
+ public void startFocusAndMetering(@NonNull FocusMeteringAction action) {
+ mExecutor.execute(new Runnable() {
+ @Override
+ public void run() {
+ mFocusMeteringControl.startFocusAndMetering(action, mPreviewAspectRatio);
+ }
+ });
+ }
+
+ @Override
+ public void cancelFocusAndMetering() {
+ mExecutor.execute(new Runnable() {
+ @Override
+ public void run() {
+ mFocusMeteringControl.cancelFocusAndMetering();
+ }
+ });
+ }
+
/** {@inheritDoc} */
@Override
public void setCropRegion(@Nullable final Rect crop) {
@@ -138,44 +138,6 @@
});
}
- /** {@inheritDoc} */
- @SuppressLint("LambdaLast") // Remove after https://issuetracker.google.com/135275901
- @Override
- public void focus(
- @NonNull final Rect focus,
- @NonNull final Rect metering,
- @NonNull final Executor userListenerExecutor,
- @NonNull final OnFocusListener listener) {
- mExecutor.execute(new Runnable() {
- @Override
- public void run() {
- focusInternal(focus, metering, userListenerExecutor, listener);
- }
- });
- }
-
- @Override
- public void focus(@NonNull Rect focus, @NonNull Rect metering) {
- mExecutor.execute(new Runnable() {
- @Override
- public void run() {
- // Listener executor won't be called, so its ok use pass direct executor here.
- focusInternal(focus, metering, CameraXExecutors.directExecutor(), null);
- }
- });
- }
-
- /** Cancels the focus operation. */
- @VisibleForTesting
- void cancelFocus() {
- mExecutor.execute(new Runnable() {
- @Override
- public void run() {
- cancelFocusInternal();
- }
- });
- }
-
@NonNull
@Override
public FlashMode getFlashMode() {
@@ -217,11 +179,6 @@
return mIsTorchOn;
}
- @Override
- public boolean isFocusLocked() {
- return mIsFocusLocked;
- }
-
/**
* Issues a {@link CaptureRequest#CONTROL_AF_TRIGGER_START} request to start auto focus scan.
*/
@@ -230,7 +187,7 @@
mExecutor.execute(new Runnable() {
@Override
public void run() {
- triggerAfInternal();
+ mFocusMeteringControl.triggerAf();
}
});
}
@@ -244,7 +201,7 @@
mExecutor.execute(new Runnable() {
@Override
public void run() {
- triggerAePrecaptureInternal();
+ mFocusMeteringControl.triggerAePrecapture();
}
});
}
@@ -260,7 +217,8 @@
mExecutor.execute(new Runnable() {
@Override
public void run() {
- cancelAfAeTriggerInternal(cancelAfTrigger, cancelAePrecaptureTrigger);
+ mFocusMeteringControl.cancelAfAeTrigger(cancelAfTrigger,
+ cancelAePrecaptureTrigger);
}
});
}
@@ -311,143 +269,30 @@
updateSessionConfig();
}
- @SuppressWarnings("WeakerAccess") /* synthetic accessor */
@WorkerThread
- void focusInternal(
- final Rect focus,
- final Rect metering,
- @NonNull final Executor listenerExecutor,
- @Nullable final OnFocusListener listener) {
- mSessionCallback.removeListener(mSessionListenerForFocus);
-
- cancelFocusTimeout();
-
- mAfRect = new MeteringRectangle(focus, MeteringRectangle.METERING_WEIGHT_MAX);
- mAeRect = new MeteringRectangle(metering, MeteringRectangle.METERING_WEIGHT_MAX);
- mAwbRect = new MeteringRectangle(metering, MeteringRectangle.METERING_WEIGHT_MAX);
- Log.d(TAG, "Setting new AF rectangle: " + mAfRect);
- Log.d(TAG, "Setting new AE rectangle: " + mAeRect);
- Log.d(TAG, "Setting new AWB rectangle: " + mAwbRect);
-
- mCurrentAfState = CaptureResult.CONTROL_AF_STATE_INACTIVE;
- mIsFocusLocked = true;
-
- if (listener != null) {
-
- mSessionListenerForFocus =
- new CaptureResultListener() {
- // Will be called on mExecutor since mSessionCallback was created with
- // mExecutor
- @WorkerThread
- @Override
- public boolean onCaptureResult(TotalCaptureResult result) {
- Integer afState = result.get(CaptureResult.CONTROL_AF_STATE);
- if (afState == null) {
- return false;
- }
-
- if (mCurrentAfState == CaptureResult.CONTROL_AF_STATE_ACTIVE_SCAN) {
- if (afState == CaptureResult.CONTROL_AF_STATE_FOCUSED_LOCKED) {
- listenerExecutor.execute(
- new Runnable() {
- @Override
- public void run() {
- listener.onFocusLocked(mAfRect.getRect());
- }
- });
- return true; // finished
- } else if (afState
- == CaptureResult.CONTROL_AF_STATE_NOT_FOCUSED_LOCKED) {
- listenerExecutor.execute(
- new Runnable() {
- @Override
- public void run() {
- listener.onFocusUnableToLock(
- mAfRect.getRect());
- }
- });
- return true; // finished
- }
- }
- if (!mCurrentAfState.equals(afState)) {
- mCurrentAfState = afState;
- }
- return false; // continue checking
- }
- };
-
- mSessionCallback.addListener(mSessionListenerForFocus);
+ @NonNull
+ Rect getCropSensorRegion() {
+ Rect cropRect = mCropRect;
+ if (cropRect == null) {
+ cropRect = getSensorRect();
}
- updateSessionConfig();
-
- triggerAfInternal();
- if (mFocusTimeoutMs > 0) {
- final long timeoutId = ++mFocusTimeoutCounter;
- final Runnable timeoutRunnable = new Runnable() {
- @Override
- public void run() {
- mExecutor.execute(new Runnable() {
- @WorkerThread
- @Override
- public void run() {
- if (timeoutId == mFocusTimeoutCounter) {
- cancelFocusInternal();
-
- mSessionCallback.removeListener(mSessionListenerForFocus);
-
- if (listener != null
- && mCurrentAfState
- == CaptureResult.CONTROL_AF_STATE_ACTIVE_SCAN) {
- listenerExecutor.execute(
- new Runnable() {
- @Override
- public void run() {
- listener.onFocusTimedOut(mAfRect.getRect());
- }
- });
- }
- }
- }
- });
- }
- };
-
- mFocusTimeoutHandle = mScheduler.schedule(timeoutRunnable, mFocusTimeoutMs,
- TimeUnit.MILLISECONDS);
- }
+ return cropRect;
}
@WorkerThread
- private void cancelFocusTimeout() {
- if (mFocusTimeoutHandle != null) {
- mFocusTimeoutHandle.cancel(/*mayInterruptIfRunning=*/true);
- mFocusTimeoutHandle = null;
- }
+ @NonNull
+ Rect getSensorRect() {
+ return mCameraCharacteristics.get(CameraCharacteristics.SENSOR_INFO_ACTIVE_ARRAY_SIZE);
}
- @SuppressWarnings("WeakerAccess") /* synthetic accessor */
@WorkerThread
- void cancelFocusInternal() {
- cancelFocusTimeout();
+ void removeCaptureResultListener(@NonNull CaptureResultListener listener) {
+ mSessionCallback.removeListener(listener);
+ }
- MeteringRectangle zeroRegion =
- new MeteringRectangle(new Rect(), MeteringRectangle.METERING_WEIGHT_DONT_CARE);
- mAfRect = zeroRegion;
- mAeRect = zeroRegion;
- mAwbRect = zeroRegion;
-
- // Send a single request to cancel af process
- CaptureConfig.Builder singleRequestBuilder = createCaptureBuilderWithSharedOptions();
- singleRequestBuilder.setTemplateType(getDefaultTemplate());
- singleRequestBuilder.setUseRepeatingSurface(true);
- Camera2Config.Builder configBuilder = new Camera2Config.Builder();
- configBuilder.setCaptureRequestOption(CaptureRequest.CONTROL_AF_TRIGGER,
- CaptureRequest.CONTROL_AF_TRIGGER_CANCEL);
- singleRequestBuilder.addImplementationOptions(configBuilder.build());
- notifyCaptureRequests(Collections.singletonList(singleRequestBuilder.build()));
-
- mIsFocusLocked = false;
- updateSessionConfig();
+ @WorkerThread
+ void addCaptureResultListener(@NonNull CaptureResultListener listener) {
+ mSessionCallback.addListener(listener);
}
@SuppressWarnings("WeakerAccess") /* synthetic accessor */
@@ -460,7 +305,7 @@
singleRequestBuilder.setUseRepeatingSurface(true);
Camera2Config.Builder configBuilder = new Camera2Config.Builder();
configBuilder.setCaptureRequestOption(CaptureRequest.CONTROL_AE_MODE,
- CaptureRequest.CONTROL_AE_MODE_ON);
+ getSupportedAeMode(CaptureRequest.CONTROL_AE_MODE_ON));
configBuilder.setCaptureRequestOption(CaptureRequest.FLASH_MODE,
CaptureRequest.FLASH_MODE_OFF);
singleRequestBuilder.addImplementationOptions(configBuilder.build());
@@ -469,52 +314,6 @@
updateSessionConfig();
}
- @SuppressWarnings("WeakerAccess") /* synthetic accessor */
- @WorkerThread
- void triggerAfInternal() {
- CaptureConfig.Builder builder = createCaptureBuilderWithSharedOptions();
- builder.setTemplateType(getDefaultTemplate());
- builder.setUseRepeatingSurface(true);
- Camera2Config.Builder configBuilder = new Camera2Config.Builder();
- configBuilder.setCaptureRequestOption(CaptureRequest.CONTROL_AF_TRIGGER,
- CaptureRequest.CONTROL_AF_TRIGGER_START);
- builder.addImplementationOptions(configBuilder.build());
- notifyCaptureRequests(Collections.singletonList(builder.build()));
- }
-
- @SuppressWarnings("WeakerAccess") /* synthetic accessor */
- @WorkerThread
- void triggerAePrecaptureInternal() {
- CaptureConfig.Builder builder = createCaptureBuilderWithSharedOptions();
- builder.setTemplateType(getDefaultTemplate());
- builder.setUseRepeatingSurface(true);
- Camera2Config.Builder configBuilder = new Camera2Config.Builder();
- configBuilder.setCaptureRequestOption(CaptureRequest.CONTROL_AE_PRECAPTURE_TRIGGER,
- CaptureRequest.CONTROL_AE_PRECAPTURE_TRIGGER_START);
- builder.addImplementationOptions(configBuilder.build());
- notifyCaptureRequests(Collections.singletonList(builder.build()));
- }
-
- @SuppressWarnings("WeakerAccess") /* synthetic accessor */
- @WorkerThread
- void cancelAfAeTriggerInternal(final boolean cancelAfTrigger,
- final boolean cancelAePrecaptureTrigger) {
- CaptureConfig.Builder builder = createCaptureBuilderWithSharedOptions();
- builder.setUseRepeatingSurface(true);
- builder.setTemplateType(getDefaultTemplate());
-
- Camera2Config.Builder configBuilder = new Camera2Config.Builder();
- if (cancelAfTrigger) {
- configBuilder.setCaptureRequestOption(CaptureRequest.CONTROL_AF_TRIGGER,
- CaptureRequest.CONTROL_AF_TRIGGER_CANCEL);
- }
- if (Build.VERSION.SDK_INT >= 23 && cancelAePrecaptureTrigger) {
- configBuilder.setCaptureRequestOption(CaptureRequest.CONTROL_AE_PRECAPTURE_TRIGGER,
- CaptureRequest.CONTROL_AE_PRECAPTURE_TRIGGER_CANCEL);
- }
- builder.addImplementationOptions(configBuilder.build());
- notifyCaptureRequests(Collections.singletonList(builder.build()));
- }
@SuppressWarnings("WeakerAccess") /* synthetic accessor */
@WorkerThread
@@ -542,11 +341,8 @@
builder.setCaptureRequestOption(
CaptureRequest.CONTROL_MODE, CaptureRequest.CONTROL_MODE_AUTO);
- builder.setCaptureRequestOption(
- CaptureRequest.CONTROL_AF_MODE,
- isFocusLocked()
- ? CaptureRequest.CONTROL_AF_MODE_AUTO
- : CaptureRequest.CONTROL_AF_MODE_CONTINUOUS_PICTURE);
+ // AF Mode is assigned in mFocusMeteringControl.
+ mFocusMeteringControl.addFocusMeteringOptions(builder);
int aeMode = CaptureRequest.CONTROL_AE_MODE_ON;
if (mIsTorchOn) {
@@ -565,23 +361,11 @@
break;
}
}
- builder.setCaptureRequestOption(CaptureRequest.CONTROL_AE_MODE, aeMode);
+ builder.setCaptureRequestOption(CaptureRequest.CONTROL_AE_MODE, getSupportedAeMode(aeMode));
builder.setCaptureRequestOption(
- CaptureRequest.CONTROL_AWB_MODE, CaptureRequest.CONTROL_AWB_MODE_AUTO);
-
- if (mAfRect != null) {
- builder.setCaptureRequestOption(
- CaptureRequest.CONTROL_AF_REGIONS, new MeteringRectangle[]{mAfRect});
- }
- if (mAeRect != null) {
- builder.setCaptureRequestOption(
- CaptureRequest.CONTROL_AE_REGIONS, new MeteringRectangle[]{mAeRect});
- }
- if (mAwbRect != null) {
- builder.setCaptureRequestOption(
- CaptureRequest.CONTROL_AWB_REGIONS, new MeteringRectangle[]{mAwbRect});
- }
+ CaptureRequest.CONTROL_AWB_MODE,
+ getSupportedAwbMode(CaptureRequest.CONTROL_AWB_MODE_AUTO));
if (mCropRect != null) {
builder.setCaptureRequestOption(CaptureRequest.SCALER_CROP_REGION, mCropRect);
@@ -590,15 +374,117 @@
return builder.build();
}
+ /**
+ * Returns a supported AF mode which will be preferredMode if it is supported.
+ *
+ * <p><pre>If preferredMode is not supported, fallback with the following priority (highest to
+ * lowest).
+ * 1) {@link CaptureRequest#CONTROL_AF_MODE_CONTINUOUS_PICTURE}
+ * 2) {@link CaptureRequest#CONTROL_AF_MODE_AUTO)}
+ * 3) {@link CaptureRequest#CONTROL_AF_MODE_OFF}
+ * </pre>
+ */
+ @WorkerThread
+ int getSupportedAfMode(int preferredMode) {
+ int[] modes = mCameraCharacteristics.get(CameraCharacteristics.CONTROL_AF_AVAILABLE_MODES);
+ if (modes == null) {
+ return CaptureRequest.CONTROL_AF_MODE_OFF;
+ }
+
+ // if preferredMode is supported, use it
+ if (isModeInList(preferredMode, modes)) {
+ return preferredMode;
+ }
+
+ // if not found, priority is CONTINUOUS_PICTURE > AUTO > OFF
+ if (isModeInList(CaptureRequest.CONTROL_AF_MODE_CONTINUOUS_PICTURE, modes)) {
+ return CaptureRequest.CONTROL_AF_MODE_CONTINUOUS_PICTURE;
+ } else if (isModeInList(CaptureRequest.CONTROL_AF_MODE_AUTO, modes)) {
+ return CaptureRequest.CONTROL_AF_MODE_AUTO;
+ }
+
+ return CaptureRequest.CONTROL_AF_MODE_OFF;
+ }
+
+ /**
+ * Returns a supported AE mode which will be preferredMode if it is supported.
+ *
+ * <p><pre>If preferredMode is not supported, fallback with the following priority (highest to
+ * lowest).
+ * 1) {@link CaptureRequest#CONTROL_AE_MODE_ON}
+ * 2) {@link CaptureRequest#CONTROL_AE_MODE_OFF)}
+ * </pre>
+ */
+ @WorkerThread
+ private int getSupportedAeMode(int preferredMode) {
+ int[] modes = mCameraCharacteristics.get(CameraCharacteristics.CONTROL_AE_AVAILABLE_MODES);
+
+ if (modes == null) {
+ return CaptureRequest.CONTROL_AE_MODE_OFF;
+ }
+
+ // if preferredMode is supported, use it
+ if (isModeInList(preferredMode, modes)) {
+ return preferredMode;
+ }
+
+ // if not found, priority is AE_ON > AE_OFF
+ if (isModeInList(CaptureRequest.CONTROL_AE_MODE_ON, modes)) {
+ return CaptureRequest.CONTROL_AE_MODE_ON;
+ }
+
+ return CaptureRequest.CONTROL_AE_MODE_OFF;
+ }
+
+ /**
+ * Returns a supported AWB mode which will be preferredMode if it is supported.
+ *
+ * <p><pre>If preferredMode is not supported, fallback with the following priority (highest to
+ * lowest).
+ * 1) {@link CaptureRequest#CONTROL_AWB_MODE_AUTO}
+ * 2) {@link CaptureRequest#CONTROL_AWB_MODE_OFF)}
+ * </pre>
+ */
+ @WorkerThread
+ private int getSupportedAwbMode(int preferredMode) {
+ int[] modes = mCameraCharacteristics.get(CameraCharacteristics.CONTROL_AWB_AVAILABLE_MODES);
+
+ if (modes == null) {
+ return CaptureRequest.CONTROL_AWB_MODE_OFF;
+ }
+
+ // if preferredMode is supported, use it
+ if (isModeInList(preferredMode, modes)) {
+ return preferredMode;
+ }
+
+ // if not found, priority is AWB_AUTO > AWB_OFF
+ if (isModeInList(CaptureRequest.CONTROL_AWB_MODE_AUTO, modes)) {
+ return CaptureRequest.CONTROL_AWB_MODE_AUTO;
+ }
+
+ return CaptureRequest.CONTROL_AWB_MODE_OFF;
+ }
+
+ @WorkerThread
+ private boolean isModeInList(int mode, int[] modeList) {
+ for (int m : modeList) {
+ if (mode == m) {
+ return true;
+ }
+ }
+ return false;
+ }
+
/** An interface to listen to camera capture results. */
- private interface CaptureResultListener {
+ interface CaptureResultListener {
/**
* Callback to handle camera capture results.
*
* @param captureResult camera capture result.
* @return true to finish listening, false to continue listening.
*/
- boolean onCaptureResult(TotalCaptureResult captureResult);
+ boolean onCaptureResult(@NonNull TotalCaptureResult captureResult);
}
static final class CameraControlSessionCallback extends CaptureCallback {
@@ -611,12 +497,12 @@
}
@WorkerThread
- void addListener(CaptureResultListener listener) {
+ void addListener(@NonNull CaptureResultListener listener) {
mResultListeners.add(listener);
}
@WorkerThread
- void removeListener(CaptureResultListener listener) {
+ void removeListener(@NonNull CaptureResultListener listener) {
mResultListeners.remove(listener);
}
@@ -642,8 +528,6 @@
}
}
});
-
-
}
}
}
diff --git a/camera/camera-camera2/src/main/java/androidx/camera/camera2/impl/Camera2CameraInfo.java b/camera/camera-camera2/src/main/java/androidx/camera/camera2/impl/Camera2CameraInfo.java
index 6722377..d5251f2 100644
--- a/camera/camera-camera2/src/main/java/androidx/camera/camera2/impl/Camera2CameraInfo.java
+++ b/camera/camera-camera2/src/main/java/androidx/camera/camera2/impl/Camera2CameraInfo.java
@@ -70,9 +70,7 @@
@Override
public int getSensorRotationDegrees(@RotationValue int relativeRotation) {
- Integer sensorOrientation =
- mCameraCharacteristics.get(CameraCharacteristics.SENSOR_ORIENTATION);
- Preconditions.checkNotNull(sensorOrientation);
+ Integer sensorOrientation = getSensorOrientation();
int relativeRotationDegrees =
CameraOrientationUtil.surfaceRotationToDegrees(relativeRotation);
// Currently this assumes that a back-facing camera is always opposite to the screen.
@@ -85,6 +83,13 @@
isOppositeFacingScreen);
}
+ int getSensorOrientation() {
+ Integer sensorOrientation =
+ mCameraCharacteristics.get(CameraCharacteristics.SENSOR_ORIENTATION);
+ Preconditions.checkNotNull(sensorOrientation);
+ return sensorOrientation;
+ }
+
int getSupportedHardwareLevel() {
Integer deviceLevel =
mCameraCharacteristics.get(CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL);
diff --git a/camera/camera-camera2/src/main/java/androidx/camera/camera2/impl/Camera2CaptureCallbacks.java b/camera/camera-camera2/src/main/java/androidx/camera/camera2/impl/Camera2CaptureCallbacks.java
index 20ff7d5..9cb9210 100644
--- a/camera/camera-camera2/src/main/java/androidx/camera/camera2/impl/Camera2CaptureCallbacks.java
+++ b/camera/camera-camera2/src/main/java/androidx/camera/camera2/impl/Camera2CaptureCallbacks.java
@@ -24,6 +24,7 @@
import android.os.Build;
import android.view.Surface;
+import androidx.annotation.NonNull;
import androidx.annotation.RequiresApi;
import androidx.annotation.RestrictTo;
import androidx.annotation.RestrictTo.Scope;
@@ -43,6 +44,7 @@
}
/** Returns a session capture callback which does nothing. */
+ @NonNull
public static CameraCaptureSession.CaptureCallback createNoOpCallback() {
return new NoOpSessionCaptureCallback();
}
@@ -54,8 +56,9 @@
}
/** Returns a session capture callback which calls a list of other callbacks. */
+ @NonNull
public static CameraCaptureSession.CaptureCallback createComboCallback(
- CameraCaptureSession.CaptureCallback... callbacks) {
+ @NonNull CameraCaptureSession.CaptureCallback... callbacks) {
return createComboCallback(Arrays.asList(callbacks));
}
@@ -63,41 +66,45 @@
extends CameraCaptureSession.CaptureCallback {
@Override
public void onCaptureBufferLost(
- CameraCaptureSession session,
- CaptureRequest request,
- Surface surface,
+ @NonNull CameraCaptureSession session,
+ @NonNull CaptureRequest request,
+ @NonNull Surface surface,
long frame) {
}
@Override
public void onCaptureCompleted(
- CameraCaptureSession session, CaptureRequest request, TotalCaptureResult result) {
+ @NonNull CameraCaptureSession session, @NonNull CaptureRequest request,
+ @NonNull TotalCaptureResult result) {
}
@Override
public void onCaptureFailed(
- CameraCaptureSession session, CaptureRequest request, CaptureFailure failure) {
+ @NonNull CameraCaptureSession session, @NonNull CaptureRequest request,
+ @NonNull CaptureFailure failure) {
}
@Override
public void onCaptureProgressed(
- CameraCaptureSession session,
- CaptureRequest request,
- CaptureResult partialResult) {
+ @NonNull CameraCaptureSession session,
+ @NonNull CaptureRequest request,
+ @NonNull CaptureResult partialResult) {
}
@Override
- public void onCaptureSequenceAborted(CameraCaptureSession session, int sequenceId) {
+ public void onCaptureSequenceAborted(@NonNull CameraCaptureSession session,
+ int sequenceId) {
}
@Override
public void onCaptureSequenceCompleted(
- CameraCaptureSession session, int sequenceId, long frame) {
+ @NonNull CameraCaptureSession session, int sequenceId, long frame) {
}
@Override
public void onCaptureStarted(
- CameraCaptureSession session, CaptureRequest request, long timestamp, long frame) {
+ @NonNull CameraCaptureSession session, @NonNull CaptureRequest request,
+ long timestamp, long frame) {
}
}
@@ -117,7 +124,8 @@
@RequiresApi(api = Build.VERSION_CODES.N)
@Override
public void onCaptureBufferLost(
- CameraCaptureSession session, CaptureRequest request, Surface surface, long frame) {
+ @NonNull CameraCaptureSession session, @NonNull CaptureRequest request,
+ @NonNull Surface surface, long frame) {
for (CameraCaptureSession.CaptureCallback callback : mCallbacks) {
callback.onCaptureBufferLost(session, request, surface, frame);
}
@@ -125,7 +133,8 @@
@Override
public void onCaptureCompleted(
- CameraCaptureSession session, CaptureRequest request, TotalCaptureResult result) {
+ @NonNull CameraCaptureSession session, @NonNull CaptureRequest request,
+ @NonNull TotalCaptureResult result) {
for (CameraCaptureSession.CaptureCallback callback : mCallbacks) {
callback.onCaptureCompleted(session, request, result);
}
@@ -133,7 +142,8 @@
@Override
public void onCaptureFailed(
- CameraCaptureSession session, CaptureRequest request, CaptureFailure failure) {
+ @NonNull CameraCaptureSession session, @NonNull CaptureRequest request,
+ @NonNull CaptureFailure failure) {
for (CameraCaptureSession.CaptureCallback callback : mCallbacks) {
callback.onCaptureFailed(session, request, failure);
}
@@ -141,14 +151,16 @@
@Override
public void onCaptureProgressed(
- CameraCaptureSession session, CaptureRequest request, CaptureResult partialResult) {
+ @NonNull CameraCaptureSession session, @NonNull CaptureRequest request,
+ @NonNull CaptureResult partialResult) {
for (CameraCaptureSession.CaptureCallback callback : mCallbacks) {
callback.onCaptureProgressed(session, request, partialResult);
}
}
@Override
- public void onCaptureSequenceAborted(CameraCaptureSession session, int sequenceId) {
+ public void onCaptureSequenceAborted(@NonNull CameraCaptureSession session,
+ int sequenceId) {
for (CameraCaptureSession.CaptureCallback callback : mCallbacks) {
callback.onCaptureSequenceAborted(session, sequenceId);
}
@@ -156,7 +168,7 @@
@Override
public void onCaptureSequenceCompleted(
- CameraCaptureSession session, int sequenceId, long frame) {
+ @NonNull CameraCaptureSession session, int sequenceId, long frame) {
for (CameraCaptureSession.CaptureCallback callback : mCallbacks) {
callback.onCaptureSequenceCompleted(session, sequenceId, frame);
}
@@ -164,7 +176,8 @@
@Override
public void onCaptureStarted(
- CameraCaptureSession session, CaptureRequest request, long timestamp, long frame) {
+ @NonNull CameraCaptureSession session, @NonNull CaptureRequest request,
+ long timestamp, long frame) {
for (CameraCaptureSession.CaptureCallback callback : mCallbacks) {
callback.onCaptureStarted(session, request, timestamp, frame);
}
diff --git a/camera/camera-camera2/src/main/java/androidx/camera/camera2/impl/Camera2Initializer.java b/camera/camera-camera2/src/main/java/androidx/camera/camera2/impl/Camera2Initializer.java
index d2dd131..82622c7 100644
--- a/camera/camera-camera2/src/main/java/androidx/camera/camera2/impl/Camera2Initializer.java
+++ b/camera/camera-camera2/src/main/java/androidx/camera/camera2/impl/Camera2Initializer.java
@@ -23,6 +23,7 @@
import android.net.Uri;
import android.util.Log;
+import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.RestrictTo;
import androidx.camera.camera2.Camera2AppConfig;
@@ -48,7 +49,7 @@
@Nullable
@Override
public Cursor query(
- Uri uri,
+ @NonNull Uri uri,
@Nullable String[] strings,
@Nullable String s,
@Nullable String[] strings1,
@@ -58,24 +59,24 @@
@Nullable
@Override
- public String getType(Uri uri) {
+ public String getType(@NonNull Uri uri) {
return null;
}
@Nullable
@Override
- public Uri insert(Uri uri, @Nullable ContentValues contentValues) {
+ public Uri insert(@NonNull Uri uri, @Nullable ContentValues contentValues) {
return null;
}
@Override
- public int delete(Uri uri, @Nullable String s, @Nullable String[] strings) {
+ public int delete(@NonNull Uri uri, @Nullable String s, @Nullable String[] strings) {
return 0;
}
@Override
public int update(
- Uri uri,
+ @NonNull Uri uri,
@Nullable ContentValues contentValues,
@Nullable String s,
@Nullable String[] strings) {
diff --git a/camera/camera-camera2/src/main/java/androidx/camera/camera2/impl/CameraAvailabilityRegistry.java b/camera/camera-camera2/src/main/java/androidx/camera/camera2/impl/CameraAvailabilityRegistry.java
index b76faa5..d7a0ba6 100644
--- a/camera/camera-camera2/src/main/java/androidx/camera/camera2/impl/CameraAvailabilityRegistry.java
+++ b/camera/camera-camera2/src/main/java/androidx/camera/camera2/impl/CameraAvailabilityRegistry.java
@@ -153,9 +153,10 @@
mDebugString.append(
"-------------------------------------------------------------------\n");
}
- // Count the number of cameras that are not in a CLOSED or OPENING state. All cameras
- // that are in a CLOSING or RELEASING state may have previously been open, so we will
- // count them as open until they reach a CLOSED or RELEASED state.
+ // Count the number of cameras that are not in a closed state state. Closed states are
+ // considered to be CLOSED, PENDING_OPEN or OPENING, since we can't guarantee a camera
+ // has actually be open in these states. All cameras that are in a CLOSING or RELEASING
+ // state may have previously been open, so we will count them as open.
int openCount = 0;
for (Map.Entry<BaseCamera, BaseCamera.State> entry : mCameraStates.entrySet()) {
if (DEBUG) {
@@ -166,7 +167,8 @@
stateString));
}
if (entry.getValue() != BaseCamera.State.CLOSED
- && entry.getValue() != BaseCamera.State.OPENING) {
+ && entry.getValue() != BaseCamera.State.OPENING
+ && entry.getValue() != BaseCamera.State.PENDING_OPEN) {
openCount++;
}
}
diff --git a/camera/camera-camera2/src/main/java/androidx/camera/camera2/impl/CameraBurstCaptureCallback.java b/camera/camera-camera2/src/main/java/androidx/camera/camera2/impl/CameraBurstCaptureCallback.java
index ba38457..c881b2c 100644
--- a/camera/camera-camera2/src/main/java/androidx/camera/camera2/impl/CameraBurstCaptureCallback.java
+++ b/camera/camera-camera2/src/main/java/androidx/camera/camera2/impl/CameraBurstCaptureCallback.java
@@ -53,7 +53,8 @@
@RequiresApi(api = Build.VERSION_CODES.N)
@Override
public void onCaptureBufferLost(
- CameraCaptureSession session, CaptureRequest request, Surface surface, long frame) {
+ @NonNull CameraCaptureSession session, @NonNull CaptureRequest request,
+ @NonNull Surface surface, long frame) {
for (CameraCaptureSession.CaptureCallback callback : getCallbacks(request)) {
callback.onCaptureBufferLost(session, request, surface, frame);
}
diff --git a/camera/camera-camera2/src/main/java/androidx/camera/camera2/impl/CameraEventCallbacks.java b/camera/camera-camera2/src/main/java/androidx/camera/camera2/impl/CameraEventCallbacks.java
index a5187c8d..3955a31 100644
--- a/camera/camera-camera2/src/main/java/androidx/camera/camera2/impl/CameraEventCallbacks.java
+++ b/camera/camera-camera2/src/main/java/androidx/camera/camera2/impl/CameraEventCallbacks.java
@@ -40,15 +40,18 @@
}
/** Returns a camera event callback which calls a list of other callbacks. */
+ @NonNull
public ComboCameraEventCallback createComboCallback() {
return new ComboCameraEventCallback(getAllItems());
}
/** Returns a camera event callback which does nothing. */
+ @NonNull
public static CameraEventCallbacks createEmptyCallback() {
return new CameraEventCallbacks();
}
+ @NonNull
@Override
public MultiValueSet<CameraEventCallback> clone() {
CameraEventCallbacks ret = createEmptyCallback();
diff --git a/camera/camera-camera2/src/main/java/androidx/camera/camera2/impl/CaptureSession.java b/camera/camera-camera2/src/main/java/androidx/camera/camera2/impl/CaptureSession.java
index 5a730a7..9e3403f 100644
--- a/camera/camera-camera2/src/main/java/androidx/camera/camera2/impl/CaptureSession.java
+++ b/camera/camera-camera2/src/main/java/androidx/camera/camera2/impl/CaptureSession.java
@@ -22,10 +22,6 @@
import android.hardware.camera2.CameraDevice;
import android.hardware.camera2.CaptureRequest;
import android.hardware.camera2.TotalCaptureResult;
-import android.hardware.camera2.params.OutputConfiguration;
-import android.hardware.camera2.params.SessionConfiguration;
-import android.os.Build;
-import android.os.Handler;
import android.util.Log;
import android.view.Surface;
@@ -33,6 +29,10 @@
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.camera.camera2.Camera2Config;
+import androidx.camera.camera2.impl.compat.CameraCaptureSessionCompat;
+import androidx.camera.camera2.impl.compat.CameraDeviceCompat;
+import androidx.camera.camera2.impl.compat.params.OutputConfigurationCompat;
+import androidx.camera.camera2.impl.compat.params.SessionConfigurationCompat;
import androidx.camera.core.CameraCaptureCallback;
import androidx.camera.core.CameraCaptureSessionStateCallbacks;
import androidx.camera.core.CaptureConfig;
@@ -66,17 +66,12 @@
*/
final class CaptureSession {
private static final String TAG = "CaptureSession";
-
- /** Handler for all the callbacks from the {@link CameraCaptureSession}. */
- @Nullable
- private final Handler mHandler;
- /** An adapter to pass the task to the handler. */
- @Nullable
+ /** Lock on whether the camera is open or closed. */
+ final Object mStateLock = new Object();
+ /** Executor for all the callbacks from the {@link CameraCaptureSession}. */
private final Executor mExecutor;
/** The configuration for the currently issued single capture requests. */
private final List<CaptureConfig> mCaptureConfigs = new ArrayList<>();
- /** Lock on whether the camera is open or closed. */
- final Object mStateLock = new Object();
/** Callback for handling image captures. */
private final CameraCaptureSession.CaptureCallback mCaptureCallback =
new CaptureCallback() {
@@ -121,15 +116,17 @@
/**
* Constructor for CaptureSession.
*
- * @param handler The handler is responsible for queuing up callbacks from capture requests. If
- * this is null then when asynchronous methods are called on this session they
- * will attempt
- * to use the current thread's looper.
+ * @param executor The executor is responsible for queuing up callbacks from capture requests.
*/
- CaptureSession(@Nullable Handler handler) {
- mHandler = handler;
+ CaptureSession(@NonNull Executor executor) {
mState = State.INITIALIZED;
- mExecutor = (handler != null) ? CameraXExecutors.newHandlerExecutor(handler) : null;
+
+ // Ensure tasks posted to the executor are executed sequentially.
+ if (CameraXExecutors.isSequentialExecutor(executor)) {
+ mExecutor = executor;
+ } else {
+ mExecutor = CameraXExecutors.newSequentialExecutor(executor);
+ }
}
/**
@@ -246,45 +243,39 @@
List<CaptureConfig> presetList =
eventCallbacks.createComboCallback().onPresetSession();
- if (Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.P
- && !presetList.isEmpty()) {
+ // Generate the CaptureRequest builder from repeating request since Android
+ // recommend use the same template type as the initial capture request. The
+ // tag and output targets would be ignored by default.
+ CaptureConfig.Builder captureConfigBuilder = CaptureConfig.Builder.from(
+ sessionConfig.getRepeatingCaptureConfig());
- // Generate the CaptureRequest builder from repeating request since Android
- // recommend use the same template type as the initial capture request. The
- // tag and output targets would be ignored by default.
- CaptureConfig.Builder captureConfigBuilder = CaptureConfig.Builder.from(
- sessionConfig.getRepeatingCaptureConfig());
-
- for (CaptureConfig config : presetList) {
- captureConfigBuilder.addImplementationOptions(
- config.getImplementationOptions());
- }
-
- CaptureRequest captureRequest =
- Camera2CaptureRequestBuilder.buildWithoutTarget(
- captureConfigBuilder.build(),
- cameraDevice);
-
- if (captureRequest != null) {
- List<OutputConfiguration> outputConfigList = new LinkedList<>();
- for (Surface surface : uniqueConfiguredSurface) {
- outputConfigList.add(new OutputConfiguration(surface));
- }
-
- SessionConfiguration sessionParameterConfiguration =
- new SessionConfiguration(SessionConfiguration.SESSION_REGULAR,
- outputConfigList, getExecutor(), comboCallback);
- sessionParameterConfiguration.setSessionParameters(captureRequest);
- cameraDevice.createCaptureSession(sessionParameterConfiguration);
- } else {
- cameraDevice.createCaptureSession(uniqueConfiguredSurface,
- comboCallback,
- mHandler);
- }
- } else {
- cameraDevice.createCaptureSession(uniqueConfiguredSurface, comboCallback,
- mHandler);
+ for (CaptureConfig config : presetList) {
+ captureConfigBuilder.addImplementationOptions(
+ config.getImplementationOptions());
}
+
+ List<OutputConfigurationCompat> outputConfigList = new LinkedList<>();
+ for (Surface surface : uniqueConfiguredSurface) {
+ outputConfigList.add(new OutputConfigurationCompat(surface));
+ }
+
+ SessionConfigurationCompat sessionConfigCompat =
+ new SessionConfigurationCompat(
+ SessionConfigurationCompat.SESSION_REGULAR,
+ outputConfigList,
+ getExecutor(),
+ comboCallback);
+
+ CaptureRequest captureRequest =
+ Camera2CaptureRequestBuilder.buildWithoutTarget(
+ captureConfigBuilder.build(),
+ cameraDevice);
+
+ if (captureRequest != null) {
+ sessionConfigCompat.setSessionParameters(captureRequest);
+ }
+
+ CameraDeviceCompat.createCaptureSession(cameraDevice, sessionConfigCompat);
break;
default:
Log.e(TAG, "Open not allowed in state: " + mState);
@@ -319,7 +310,14 @@
List<CaptureConfig> configList =
eventCallbacks.createComboCallback().onDisableSession();
if (!configList.isEmpty()) {
- issueCaptureRequests(setupConfiguredSurface(configList));
+ try {
+ issueCaptureRequests(setupConfiguredSurface(configList));
+ } catch (IllegalStateException e) {
+ // We couldn't issue the request before close the capture session,
+ // but we should continue the close flow.
+ Log.e(TAG, "Unable to issue the request before close the capture "
+ + "session", e);
+ }
}
}
// Not break close flow.
@@ -506,8 +504,8 @@
captureConfig.getCameraCaptureCallbacks(),
mCaptureCallback);
- mCameraCaptureSession.setRepeatingRequest(
- captureRequest, comboCaptureCallback, mHandler);
+ CameraCaptureSessionCompat.setSingleRepeatingRequest(mCameraCaptureSession,
+ captureRequest, mExecutor, comboCaptureCallback);
} catch (CameraAccessException e) {
Log.e(TAG, "Unable to access camera: " + e.getMessage());
Thread.dumpStack();
@@ -583,9 +581,8 @@
}
if (!captureRequests.isEmpty()) {
- mCameraCaptureSession.captureBurst(captureRequests,
- callbackAggregator,
- mHandler);
+ CameraCaptureSessionCompat.captureBurstRequests(mCameraCaptureSession,
+ captureRequests, mExecutor, callbackAggregator);
} else {
Log.d(TAG, "Skipping issuing burst request due to no valid request elements");
}
@@ -609,6 +606,7 @@
return Camera2CaptureCallbacks.createComboCallback(camera2Callbacks);
}
+
/**
* Merges the implementation options from the input {@link CaptureConfig} list.
*
diff --git a/camera/camera-camera2/src/main/java/androidx/camera/camera2/impl/FocusMeteringControl.java b/camera/camera-camera2/src/main/java/androidx/camera/camera2/impl/FocusMeteringControl.java
new file mode 100644
index 0000000..4df3d87
--- /dev/null
+++ b/camera/camera-camera2/src/main/java/androidx/camera/camera2/impl/FocusMeteringControl.java
@@ -0,0 +1,415 @@
+/*
+ * 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.camera.camera2.impl;
+
+import android.graphics.PointF;
+import android.graphics.Rect;
+import android.hardware.camera2.CameraDevice;
+import android.hardware.camera2.CaptureRequest;
+import android.hardware.camera2.CaptureResult;
+import android.hardware.camera2.TotalCaptureResult;
+import android.hardware.camera2.params.MeteringRectangle;
+import android.os.Build;
+import android.util.Rational;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.annotation.WorkerThread;
+import androidx.camera.camera2.Camera2Config;
+import androidx.camera.core.CaptureConfig;
+import androidx.camera.core.FocusMeteringAction;
+import androidx.camera.core.MeteringPoint;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.concurrent.Executor;
+import java.util.concurrent.ScheduledExecutorService;
+import java.util.concurrent.ScheduledFuture;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * Implementation of focus and metering.
+ *
+ * <p>It is intended to be used within {@link Camera2CameraControl} to implement the
+ * functionality of {@link Camera2CameraControl#startFocusAndMetering(FocusMeteringAction)} and
+ * {@link Camera2CameraControl#cancelFocusAndMetering()}. This class depends on
+ * {@link Camera2CameraControl} to provide some low-level methods such as updateSessionConfig,
+ * triggerAfInternal and cancelAfAeTriggerInternal to achieve the focus and metering functions.
+ *
+ * <p>To wait for the auto-focus lock, it calls
+ * {@link Camera2CameraControl#addCaptureResultListener(Camera2CameraControl.CaptureResultListener)}
+ * to monitor the capture result. It also requires {@link ScheduledExecutorService} to schedule the
+ * auto-cancel event and {@link Executor} to ensure all the methods within this class are called
+ * in the same thread as the Camera2CameraControl.
+ *
+ * <p>The {@link Camera2CameraControl} calls {@link FocusMeteringControl#addFocusMeteringOptions} to
+ * construct the 3A regions and append them to all repeating requests and single requests.
+ */
+class FocusMeteringControl {
+ private static final String TAG = "FocusMeteringControl";
+
+ private final Camera2CameraControl mCameraControl;
+ @SuppressWarnings("WeakerAccess") /* synthetic accessor */
+ final Executor mExecutor;
+ private final ScheduledExecutorService mScheduler;
+
+ //******************** Should only be accessed by executor (WorkThread) ****************//
+ private FocusMeteringAction mCurrentFocusMeteringAction;
+ private boolean mIsInAfAutoMode = false;
+ Integer mCurrentAfState = CaptureResult.CONTROL_AF_STATE_INACTIVE;
+ private ScheduledFuture<?> mAutoCancelHandle;
+ long mFocusTimeoutCounter = 0;
+ Camera2CameraControl.CaptureResultListener mSessionListenerForFocus = null;
+ private MeteringRectangle[] mAfRects = new MeteringRectangle[]{};
+ private MeteringRectangle[] mAeRects = new MeteringRectangle[]{};
+ private MeteringRectangle[] mAwbRects = new MeteringRectangle[]{};
+ //**************************************************************************************//
+
+ FocusMeteringControl(@NonNull Camera2CameraControl cameraControl,
+ Executor executor, ScheduledExecutorService scheduler) {
+ mCameraControl = cameraControl;
+ mExecutor = executor;
+ mScheduler = scheduler;
+ }
+
+ /**
+ * Called by {@link Camera2CameraControl} to append the 3A regions to the shared options. It
+ * applies to all repeating requests and single requests.
+ */
+ @WorkerThread
+ void addFocusMeteringOptions(@NonNull Camera2Config.Builder configBuilder) {
+ int afMode = mIsInAfAutoMode
+ ? CaptureRequest.CONTROL_AF_MODE_AUTO
+ : CaptureRequest.CONTROL_AF_MODE_CONTINUOUS_PICTURE;
+
+ configBuilder.setCaptureRequestOption(
+ CaptureRequest.CONTROL_AF_MODE, mCameraControl.getSupportedAfMode(afMode));
+
+
+ if (mAfRects.length != 0) {
+ configBuilder.setCaptureRequestOption(
+ CaptureRequest.CONTROL_AF_REGIONS, mAfRects);
+ }
+ if (mAeRects.length != 0) {
+ configBuilder.setCaptureRequestOption(
+ CaptureRequest.CONTROL_AE_REGIONS, mAeRects);
+ }
+ if (mAwbRects.length != 0) {
+ configBuilder.setCaptureRequestOption(
+ CaptureRequest.CONTROL_AWB_REGIONS, mAwbRects);
+ }
+ }
+
+ @WorkerThread
+ private PointF getFOVAdjustedPoint(@NonNull MeteringPoint meteringPoint,
+ @NonNull Rational cropRegionAspectRatio,
+ @NonNull Rational defaultAspectRatio) {
+ // Use default aspect ratio unless there is a custom aspect ratio in MeteringPoint.
+ Rational fovAspectRatio = defaultAspectRatio;
+ if (meteringPoint.getFOVAspectRatio() != null) {
+ fovAspectRatio = meteringPoint.getFOVAspectRatio();
+ }
+
+ PointF adjustedPoint = new PointF(meteringPoint.getNormalizedCropRegionX(),
+ meteringPoint.getNormalizedCropRegionY());
+ if (!fovAspectRatio.equals(cropRegionAspectRatio)) {
+
+ if (fovAspectRatio.compareTo(cropRegionAspectRatio) > 0) {
+ // FOV is more narrow than crop region, top and down side of FOV is cropped.
+ float heightOfCropRegion =
+ (float) (fovAspectRatio.doubleValue()
+ / cropRegionAspectRatio.doubleValue());
+ float top_padding = (float) ((heightOfCropRegion - 1.0) / 2);
+ adjustedPoint.y = (top_padding + adjustedPoint.y) * (1 / heightOfCropRegion);
+
+ } else {
+ // FOV is wider than crop region, left and right side of FOV is cropped.
+ float widthOfCropRegion =
+ (float) (cropRegionAspectRatio.doubleValue()
+ / fovAspectRatio.doubleValue());
+ float left_padding = (float) ((widthOfCropRegion - 1.0) / 2);
+ adjustedPoint.x = (left_padding + adjustedPoint.x) * (1f / widthOfCropRegion);
+ }
+ }
+
+ return adjustedPoint;
+ }
+
+ @WorkerThread
+ private MeteringRectangle getMeteringRect(MeteringPoint meteringPoint, PointF adjustedPoint,
+ Rect cropRegion) {
+ int centerX = (int) (cropRegion.left + adjustedPoint.x * cropRegion.width());
+ int centerY = (int) (cropRegion.top + adjustedPoint.y * cropRegion.height());
+
+ int width = (int) (meteringPoint.getSize() * cropRegion.width());
+ int height = (int) (meteringPoint.getSize() * cropRegion.height());
+
+ Rect focusRect = new Rect(centerX - width / 2, centerY - height / 2, centerX + width / 2,
+ centerY + height / 2);
+
+ focusRect.left = rangeLimit(focusRect.left, cropRegion.right, cropRegion.left);
+ focusRect.right = rangeLimit(focusRect.right, cropRegion.right, cropRegion.left);
+ focusRect.top = rangeLimit(focusRect.top, cropRegion.bottom, cropRegion.top);
+ focusRect.bottom = rangeLimit(focusRect.bottom, cropRegion.bottom, cropRegion.top);
+
+ int weight = (int) (meteringPoint.getWeight() * MeteringRectangle.METERING_WEIGHT_MAX);
+
+ weight = rangeLimit(weight, MeteringRectangle.METERING_WEIGHT_MAX,
+ MeteringRectangle.METERING_WEIGHT_MIN);
+ return new MeteringRectangle(focusRect, weight);
+ }
+
+ @WorkerThread
+ private int rangeLimit(int val, int max, int min) {
+ return Math.min(Math.max(val, min), max);
+ }
+
+ @WorkerThread
+ void startFocusAndMetering(@NonNull FocusMeteringAction action,
+ @Nullable Rational defaultAspectRatio) {
+ if (mCurrentFocusMeteringAction != null) {
+ cancelFocusAndMetering();
+ }
+ mCurrentFocusMeteringAction = action;
+
+ Rect cropSensorRegion = mCameraControl.getCropSensorRegion();
+ Rational cropRegionAspectRatio = new Rational(cropSensorRegion.width(),
+ cropSensorRegion.height());
+
+ if (defaultAspectRatio == null) {
+ defaultAspectRatio = cropRegionAspectRatio;
+ }
+
+ List<MeteringRectangle> meteringRectanglesListAF = new ArrayList<>();
+ List<MeteringRectangle> meteringRectanglesListAE = new ArrayList<>();
+ List<MeteringRectangle> meteringRectanglesListAWB = new ArrayList<>();
+
+ for (MeteringPoint meteringPoint : action.getMeteringPointsAF()) {
+ PointF adjustedPoint = getFOVAdjustedPoint(meteringPoint, cropRegionAspectRatio,
+ defaultAspectRatio);
+ MeteringRectangle meteringRectangle = getMeteringRect(meteringPoint, adjustedPoint,
+ cropSensorRegion);
+ meteringRectanglesListAF.add(meteringRectangle);
+ }
+
+ for (MeteringPoint meteringPoint : action.getMeteringPointsAE()) {
+ PointF adjustedPoint = getFOVAdjustedPoint(meteringPoint, cropRegionAspectRatio,
+ defaultAspectRatio);
+ MeteringRectangle meteringRectangle = getMeteringRect(meteringPoint, adjustedPoint,
+ cropSensorRegion);
+ meteringRectanglesListAE.add(meteringRectangle);
+ }
+
+ for (MeteringPoint meteringPoint : action.getMeteringPointsAWB()) {
+ PointF adjustedPoint = getFOVAdjustedPoint(meteringPoint, cropRegionAspectRatio,
+ defaultAspectRatio);
+ MeteringRectangle meteringRectangle = getMeteringRect(meteringPoint, adjustedPoint,
+ cropSensorRegion);
+ meteringRectanglesListAWB.add(meteringRectangle);
+ }
+
+ executeMeteringAction(meteringRectanglesListAF.toArray(
+ new MeteringRectangle[meteringRectanglesListAF.size()]),
+ meteringRectanglesListAE.toArray(
+ new MeteringRectangle[meteringRectanglesListAE.size()]),
+ meteringRectanglesListAWB.toArray(
+ new MeteringRectangle[meteringRectanglesListAWB.size()]),
+ action
+ );
+ }
+
+ @WorkerThread
+ private int getDefaultTemplate() {
+ return CameraDevice.TEMPLATE_PREVIEW;
+ }
+
+ @WorkerThread
+ void triggerAf() {
+ CaptureConfig.Builder builder = new CaptureConfig.Builder();
+ builder.setTemplateType(getDefaultTemplate());
+ builder.setUseRepeatingSurface(true);
+ Camera2Config.Builder configBuilder = new Camera2Config.Builder();
+ configBuilder.setCaptureRequestOption(CaptureRequest.CONTROL_AF_TRIGGER,
+ CaptureRequest.CONTROL_AF_TRIGGER_START);
+ builder.addImplementationOptions(configBuilder.build());
+ mCameraControl.submitCaptureRequestsInternal(Collections.singletonList(builder.build()));
+ }
+
+ @WorkerThread
+ void triggerAePrecapture() {
+ CaptureConfig.Builder builder = new CaptureConfig.Builder();
+ builder.setTemplateType(getDefaultTemplate());
+ builder.setUseRepeatingSurface(true);
+ Camera2Config.Builder configBuilder = new Camera2Config.Builder();
+ configBuilder.setCaptureRequestOption(CaptureRequest.CONTROL_AE_PRECAPTURE_TRIGGER,
+ CaptureRequest.CONTROL_AE_PRECAPTURE_TRIGGER_START);
+ builder.addImplementationOptions(configBuilder.build());
+ mCameraControl.submitCaptureRequestsInternal(Collections.singletonList(builder.build()));
+ }
+
+ @WorkerThread
+ void cancelAfAeTrigger(final boolean cancelAfTrigger,
+ final boolean cancelAePrecaptureTrigger) {
+ CaptureConfig.Builder builder = new CaptureConfig.Builder();
+ builder.setUseRepeatingSurface(true);
+ builder.setTemplateType(getDefaultTemplate());
+
+ Camera2Config.Builder configBuilder = new Camera2Config.Builder();
+ if (cancelAfTrigger) {
+ configBuilder.setCaptureRequestOption(CaptureRequest.CONTROL_AF_TRIGGER,
+ CaptureRequest.CONTROL_AF_TRIGGER_CANCEL);
+ }
+ if (Build.VERSION.SDK_INT >= 23 && cancelAePrecaptureTrigger) {
+ configBuilder.setCaptureRequestOption(CaptureRequest.CONTROL_AE_PRECAPTURE_TRIGGER,
+ CaptureRequest.CONTROL_AE_PRECAPTURE_TRIGGER_CANCEL);
+ }
+ builder.addImplementationOptions(configBuilder.build());
+ mCameraControl.submitCaptureRequestsInternal(Collections.singletonList(builder.build()));
+ }
+
+
+ @WorkerThread
+ private void disableAutoCancel() {
+ if (mAutoCancelHandle != null) {
+ mAutoCancelHandle.cancel(/*mayInterruptIfRunning=*/true);
+ mAutoCancelHandle = null;
+ }
+ }
+
+ @WorkerThread
+ void executeMeteringAction(
+ @Nullable MeteringRectangle[] afRects,
+ @Nullable MeteringRectangle[] aeRects,
+ @Nullable MeteringRectangle[] awbRects,
+ FocusMeteringAction focusMeteringAction) {
+ mCameraControl.removeCaptureResultListener(mSessionListenerForFocus);
+
+ disableAutoCancel();
+
+ if (afRects == null) {
+ mAfRects = new MeteringRectangle[]{};
+ } else {
+ mAfRects = afRects;
+ }
+
+ if (aeRects == null) {
+ mAeRects = new MeteringRectangle[]{};
+ } else {
+ mAeRects = aeRects;
+ }
+
+ if (awbRects == null) {
+ mAwbRects = new MeteringRectangle[]{};
+ } else {
+ mAwbRects = awbRects;
+ }
+
+ // Trigger AF scan if any AF points are added.
+ if (shouldTriggerAF()) {
+ mCurrentAfState = CaptureResult.CONTROL_AF_STATE_INACTIVE;
+ if (focusMeteringAction.getOnAutoFocusListener() != null) {
+ mSessionListenerForFocus =
+ new Camera2CameraControl.CaptureResultListener() {
+ // Will be called on mExecutor since mSessionCallback was created with
+ // mExecutor
+ @WorkerThread
+ @Override
+ public boolean onCaptureResult(@NonNull TotalCaptureResult result) {
+ Integer afState = result.get(CaptureResult.CONTROL_AF_STATE);
+ if (afState == null) {
+ return false;
+ }
+
+ if (mCurrentAfState == CaptureResult.CONTROL_AF_STATE_ACTIVE_SCAN) {
+ if (afState == CaptureResult.CONTROL_AF_STATE_FOCUSED_LOCKED) {
+ focusMeteringAction.notifyAutoFocusCompleted(true);
+ return true; // finished
+ } else if (afState
+ == CaptureResult.CONTROL_AF_STATE_NOT_FOCUSED_LOCKED) {
+ focusMeteringAction.notifyAutoFocusCompleted(false);
+ return true; // finished
+ }
+ }
+ if (!mCurrentAfState.equals(afState)) {
+ mCurrentAfState = afState;
+ }
+ return false; // continue checking
+ }
+ };
+
+ mCameraControl.addCaptureResultListener(mSessionListenerForFocus);
+ }
+
+ mIsInAfAutoMode = true;
+ mCameraControl.updateSessionConfig();
+ triggerAf();
+ } else {
+ // Still calls OnAutoFocusActionListener when AF is not enabled.
+ focusMeteringAction.notifyAutoFocusCompleted(false);
+ mCameraControl.updateSessionConfig();
+ }
+
+ if (focusMeteringAction.isAutoCancelEnabled()) {
+ final long timeoutId = ++mFocusTimeoutCounter;
+ final Runnable autoCancelRunnable = new Runnable() {
+ @Override
+ public void run() {
+ mExecutor.execute(new Runnable() {
+ @WorkerThread
+ @Override
+ public void run() {
+ if (timeoutId == mFocusTimeoutCounter) {
+ cancelFocusAndMetering();
+ }
+ }
+ });
+ }
+ };
+
+ mAutoCancelHandle = mScheduler.schedule(autoCancelRunnable,
+ focusMeteringAction.getAutoCancelDurationInMs(),
+ TimeUnit.MILLISECONDS);
+ }
+ }
+
+ @WorkerThread
+ private boolean shouldTriggerAF() {
+ return mAfRects.length > 0;
+ }
+
+ @WorkerThread
+ void cancelFocusAndMetering() {
+ mCameraControl.removeCaptureResultListener(mSessionListenerForFocus);
+
+ if (mCurrentFocusMeteringAction != null) {
+ mCurrentFocusMeteringAction.notifyAutoFocusCompleted(false);
+ }
+ disableAutoCancel();
+
+ if (shouldTriggerAF()) {
+ cancelAfAeTrigger(true, false);
+ }
+ mAfRects = new MeteringRectangle[]{};
+ mAeRects = new MeteringRectangle[]{};
+ mAwbRects = new MeteringRectangle[]{};
+
+ mIsInAfAutoMode = false;
+ mCameraControl.updateSessionConfig();
+ mCurrentFocusMeteringAction = null;
+ }
+}
diff --git a/camera/camera-camera2/src/test/java/androidx/camera/camera2/impl/Camera2DeviceSurfaceManagerTest.java b/camera/camera-camera2/src/test/java/androidx/camera/camera2/impl/Camera2DeviceSurfaceManagerTest.java
index 63bd355..e6d2710 100644
--- a/camera/camera-camera2/src/test/java/androidx/camera/camera2/impl/Camera2DeviceSurfaceManagerTest.java
+++ b/camera/camera-camera2/src/test/java/androidx/camera/camera2/impl/Camera2DeviceSurfaceManagerTest.java
@@ -56,6 +56,7 @@
import androidx.test.core.app.ApplicationProvider;
import androidx.test.filters.SmallTest;
+import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -132,6 +133,11 @@
setupCamera();
}
+ @After
+ public void tearDown() {
+ CameraX.deinit();
+ }
+
@Test
public void checkLegacySurfaceCombinationSupportedInLegacyDevice() {
SupportedSurfaceCombination supportedSurfaceCombination =
diff --git a/camera/camera-camera2/src/test/java/androidx/camera/camera2/impl/CameraAvailabilityRegistryTest.java b/camera/camera-camera2/src/test/java/androidx/camera/camera2/impl/CameraAvailabilityRegistryTest.java
index 4bddf68..3ab4d6d 100644
--- a/camera/camera-camera2/src/test/java/androidx/camera/camera2/impl/CameraAvailabilityRegistryTest.java
+++ b/camera/camera-camera2/src/test/java/androidx/camera/camera2/impl/CameraAvailabilityRegistryTest.java
@@ -22,6 +22,7 @@
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
+import androidx.camera.core.BaseCamera;
import androidx.camera.core.Observable;
import androidx.camera.core.impl.utils.executor.CameraXExecutors;
import androidx.camera.testing.fakes.FakeCamera;
@@ -35,17 +36,19 @@
import org.robolectric.annotation.internal.DoNotInstrument;
import org.robolectric.shadows.ShadowLooper;
+import java.util.concurrent.atomic.AtomicReference;
+
@SmallTest
@RunWith(RobolectricTestRunner.class)
@DoNotInstrument
@Config(minSdk = Build.VERSION_CODES.LOLLIPOP)
public final class CameraAvailabilityRegistryTest {
- private int mCameraCount;
+ private int mAvailableCameraCount;
private Observable.Observer<Integer> mCountObserver = new Observable.Observer<Integer>() {
@Override
public void onNewData(@Nullable Integer value) {
- mCameraCount = value;
+ mAvailableCameraCount = value;
}
@Override
@@ -56,7 +59,7 @@
@Before
public void setUp() {
- mCameraCount = 0;
+ mAvailableCameraCount = 0;
}
@Test
@@ -70,7 +73,7 @@
ShadowLooper.runUiThreadTasks();
- int initialAvailableCount = mCameraCount;
+ int initialAvailableCount = mAvailableCameraCount;
FakeCamera camera = new FakeCamera();
camera.open();
@@ -79,7 +82,7 @@
ShadowLooper.runUiThreadTasks();
- int finalAvailableCount = mCameraCount;
+ int finalAvailableCount = mAvailableCameraCount;
cameraCountObservable.removeObserver(mCountObserver);
@@ -98,7 +101,7 @@
ShadowLooper.runUiThreadTasks();
- int initialAvailableCount = mCameraCount;
+ int initialAvailableCount = mAvailableCameraCount;
FakeCamera camera = new FakeCamera();
// Do not open the camera. Leave in this state.
@@ -107,7 +110,7 @@
ShadowLooper.runUiThreadTasks();
- int finalAvailableCount = mCameraCount;
+ int finalAvailableCount = mAvailableCameraCount;
cameraCountObservable.removeObserver(mCountObserver);
@@ -131,13 +134,13 @@
ShadowLooper.runUiThreadTasks();
- int initialAvailableCount = mCameraCount;
+ int initialAvailableCount = mAvailableCameraCount;
camera.close();
ShadowLooper.runUiThreadTasks();
- int finalAvailableCount = mCameraCount;
+ int finalAvailableCount = mAvailableCameraCount;
cameraCountObservable.removeObserver(mCountObserver);
@@ -161,13 +164,13 @@
ShadowLooper.runUiThreadTasks();
- int initialAvailableCount = mCameraCount;
+ int initialAvailableCount = mAvailableCameraCount;
camera.release();
ShadowLooper.runUiThreadTasks();
- int finalAvailableCount = mCameraCount;
+ int finalAvailableCount = mAvailableCameraCount;
cameraCountObservable.removeObserver(mCountObserver);
@@ -194,10 +197,51 @@
ShadowLooper.runUiThreadTasks();
- int finalAvailableCount = mCameraCount;
+ int finalAvailableCount = mAvailableCameraCount;
cameraCountObservable.removeObserver(mCountObserver);
assertThat(finalAvailableCount).isEqualTo(0);
}
+
+ @Test
+ public void pendingOpen_isNotCountedAsOpen() {
+ CameraAvailabilityRegistry registry = new CameraAvailabilityRegistry(1,
+ CameraXExecutors.directExecutor());
+
+ Observable<Integer> cameraCountObservable = registry.getAvailableCameraCount();
+
+ cameraCountObservable.addObserver(CameraXExecutors.directExecutor(), mCountObserver);
+
+ FakeCamera camera = new FakeCamera();
+
+ AtomicReference<BaseCamera.State> cameraState = new AtomicReference<>(null);
+ Observable.Observer<BaseCamera.State> stateObserver =
+ new Observable.Observer<BaseCamera.State>() {
+
+ @Override
+ public void onNewData(@Nullable BaseCamera.State value) {
+ cameraState.set(value);
+ }
+
+ @Override
+ public void onError(@NonNull Throwable t) {
+
+ }
+ };
+
+ camera.getCameraState().addObserver(CameraXExecutors.directExecutor(), stateObserver);
+ camera.setAvailableCameraCount(0);
+ camera.open();
+
+ ShadowLooper.runUiThreadTasks();
+
+ camera.getCameraState().removeObserver(stateObserver);
+ cameraCountObservable.removeObserver(mCountObserver);
+
+ // Ensure that even though the camera is the PENDING_OPEN state, there is still 1 camera
+ // available to be opened.
+ assertThat(cameraState.get()).isEqualTo(BaseCamera.State.PENDING_OPEN);
+ assertThat(mAvailableCameraCount).isEqualTo(1);
+ }
}
diff --git a/camera/camera-camera2/src/test/java/androidx/camera/camera2/impl/DisplayOrientedMeteringPointFactoryTest.java b/camera/camera-camera2/src/test/java/androidx/camera/camera2/impl/DisplayOrientedMeteringPointFactoryTest.java
new file mode 100644
index 0000000..066d37b
--- /dev/null
+++ b/camera/camera-camera2/src/test/java/androidx/camera/camera2/impl/DisplayOrientedMeteringPointFactoryTest.java
@@ -0,0 +1,364 @@
+/*
+ * 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.camera.camera2.impl;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.Mockito.when;
+
+import android.content.Context;
+import android.os.Build;
+import android.view.Display;
+import android.view.Surface;
+import android.view.WindowManager;
+
+import androidx.camera.core.AppConfig;
+import androidx.camera.core.CameraDeviceSurfaceManager;
+import androidx.camera.core.CameraX;
+import androidx.camera.core.ConfigProvider;
+import androidx.camera.core.DisplayOrientedMeteringPointFactory;
+import androidx.camera.core.ExtendableUseCaseConfigFactory;
+import androidx.camera.core.MeteringPoint;
+import androidx.camera.core.MeteringPointFactory;
+import androidx.camera.testing.fakes.FakeCamera;
+import androidx.camera.testing.fakes.FakeCameraDeviceSurfaceManager;
+import androidx.camera.testing.fakes.FakeCameraFactory;
+import androidx.camera.testing.fakes.FakeCameraInfo;
+import androidx.camera.testing.fakes.FakeUseCaseConfig;
+import androidx.test.core.app.ApplicationProvider;
+import androidx.test.filters.SmallTest;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mockito;
+import org.robolectric.RobolectricTestRunner;
+import org.robolectric.annotation.Config;
+import org.robolectric.annotation.internal.DoNotInstrument;
+
+@SmallTest
+@RunWith(RobolectricTestRunner.class)
+@DoNotInstrument
+@Config(minSdk = Build.VERSION_CODES.LOLLIPOP)
+public class DisplayOrientedMeteringPointFactoryTest {
+ private static final float WIDTH = 480;
+ private static final float HEIGHT = 640;
+ private Context mMockContext;
+ private Display mMockDisplay;
+
+ @Before
+ public void setUp() {
+ Context context = ApplicationProvider.getApplicationContext();
+
+ // Init CameraX to inject our FakeCamera with FakeCameraInfo.
+ FakeCameraFactory fakeCameraFactory = new FakeCameraFactory();
+ fakeCameraFactory.insertBackCamera(
+ new FakeCamera(new FakeCameraInfo(90, CameraX.LensFacing.BACK), null));
+ fakeCameraFactory.insertFrontCamera(
+ new FakeCamera(new FakeCameraInfo(270, CameraX.LensFacing.FRONT), null));
+
+ CameraDeviceSurfaceManager surfaceManager = new FakeCameraDeviceSurfaceManager();
+ ExtendableUseCaseConfigFactory defaultConfigFactory = new ExtendableUseCaseConfigFactory();
+ defaultConfigFactory.installDefaultProvider(FakeUseCaseConfig.class,
+ new ConfigProvider<FakeUseCaseConfig>() {
+ @Override
+ public FakeUseCaseConfig getConfig(CameraX.LensFacing lensFacing) {
+ return new FakeUseCaseConfig.Builder().build();
+ }
+ });
+
+ AppConfig appConfig =
+ new AppConfig.Builder()
+ .setCameraFactory(fakeCameraFactory)
+ .setDeviceSurfaceManager(surfaceManager)
+ .setUseCaseConfigFactory(defaultConfigFactory)
+ .build();
+ CameraX.init(context, appConfig);
+
+ mMockDisplay = Mockito.mock(Display.class);
+ when(mMockDisplay.getRotation()).thenReturn(Surface.ROTATION_0);
+ WindowManager mockWindowManager = Mockito.mock(WindowManager.class);
+ when(mockWindowManager.getDefaultDisplay()).thenReturn(mMockDisplay);
+ mMockContext = Mockito.mock(Context.class);
+ when(mMockContext.getSystemService(Context.WINDOW_SERVICE)).thenReturn(mockWindowManager);
+ }
+
+ @After
+ public void tearDown() {
+ CameraX.deinit();
+ }
+
+ @Test
+ public void defaultWeightAndAreaSize() {
+ DisplayOrientedMeteringPointFactory factory = new DisplayOrientedMeteringPointFactory(
+ mMockContext, CameraX.LensFacing.BACK, WIDTH, HEIGHT);
+
+ MeteringPoint point = factory.createPoint(0, 0);
+ assertThat(point.getSize()).isEqualTo(MeteringPointFactory.DEFAULT_AREASIZE);
+ assertThat(point.getWeight()).isEqualTo(MeteringPointFactory.DEFAULT_WEIGHT);
+ assertThat(point.getFOVAspectRatio()).isNull();
+ }
+
+ @Test
+ public void createPointWithValidWeightAndAreaSize() {
+ DisplayOrientedMeteringPointFactory factory = new DisplayOrientedMeteringPointFactory(
+ mMockContext, CameraX.LensFacing.BACK, WIDTH, HEIGHT);
+
+ final float areaSize = 0.2f;
+ final float weight = 0.5f;
+ MeteringPoint point = factory.createPoint(0, 0, areaSize, weight);
+ assertThat(point.getSize()).isEqualTo(areaSize);
+ assertThat(point.getWeight()).isEqualTo(weight);
+ assertThat(point.getFOVAspectRatio()).isNull();
+ }
+
+ @Test
+ public void display0_back() {
+ when(mMockDisplay.getRotation()).thenReturn(Surface.ROTATION_0);
+
+ DisplayOrientedMeteringPointFactory factory = new DisplayOrientedMeteringPointFactory(
+ mMockContext, CameraX.LensFacing.BACK, WIDTH, HEIGHT);
+
+ MeteringPoint meteringPoint = factory.createPoint(0f, 0f);
+ assertThat(meteringPoint.getNormalizedCropRegionX()).isEqualTo(0f);
+ assertThat(meteringPoint.getNormalizedCropRegionY()).isEqualTo(1f);
+
+ MeteringPoint meteringPoint2 = factory.createPoint(0f, HEIGHT);
+ assertThat(meteringPoint2.getNormalizedCropRegionX()).isEqualTo(1f);
+ assertThat(meteringPoint2.getNormalizedCropRegionY()).isEqualTo(1f);
+
+ MeteringPoint meteringPoint3 = factory.createPoint(WIDTH, 0f);
+
+ assertThat(meteringPoint3.getNormalizedCropRegionX()).isEqualTo(0f);
+ assertThat(meteringPoint3.getNormalizedCropRegionY()).isEqualTo(0f);
+
+ MeteringPoint meteringPoint4 = factory.createPoint(WIDTH, HEIGHT);
+
+ assertThat(meteringPoint4.getNormalizedCropRegionX()).isEqualTo(1f);
+ assertThat(meteringPoint4.getNormalizedCropRegionY()).isEqualTo(0f);
+ }
+
+ @Test
+ public void display0_front() {
+ when(mMockDisplay.getRotation()).thenReturn(Surface.ROTATION_0);
+
+ DisplayOrientedMeteringPointFactory factory = new DisplayOrientedMeteringPointFactory(
+ mMockContext, CameraX.LensFacing.FRONT, WIDTH, HEIGHT);
+
+ MeteringPoint meteringPoint = factory.createPoint(0f, 0f);
+ assertThat(meteringPoint.getNormalizedCropRegionX()).isEqualTo(1f);
+ assertThat(meteringPoint.getNormalizedCropRegionY()).isEqualTo(1f);
+
+ MeteringPoint meteringPoint2 = factory.createPoint(0f, HEIGHT);
+ assertThat(meteringPoint2.getNormalizedCropRegionX()).isEqualTo(0f);
+ assertThat(meteringPoint2.getNormalizedCropRegionY()).isEqualTo(1f);
+
+ MeteringPoint meteringPoint3 = factory.createPoint(WIDTH, 0f);
+
+ assertThat(meteringPoint3.getNormalizedCropRegionX()).isEqualTo(1f);
+ assertThat(meteringPoint3.getNormalizedCropRegionY()).isEqualTo(0f);
+
+ MeteringPoint meteringPoint4 = factory.createPoint(WIDTH, HEIGHT);
+
+ assertThat(meteringPoint4.getNormalizedCropRegionX()).isEqualTo(0f);
+ assertThat(meteringPoint4.getNormalizedCropRegionY()).isEqualTo(0f);
+ }
+
+ @Test
+ public void display90_back() {
+ when(mMockDisplay.getRotation()).thenReturn(Surface.ROTATION_90);
+
+ DisplayOrientedMeteringPointFactory factory = new DisplayOrientedMeteringPointFactory(
+ mMockContext, CameraX.LensFacing.BACK, WIDTH, HEIGHT);
+
+ MeteringPoint meteringPoint = factory.createPoint(0f, 0f);
+ assertThat(meteringPoint.getNormalizedCropRegionX()).isEqualTo(0f);
+ assertThat(meteringPoint.getNormalizedCropRegionY()).isEqualTo(0f);
+
+ MeteringPoint meteringPoint2 = factory.createPoint(0f, HEIGHT);
+ assertThat(meteringPoint2.getNormalizedCropRegionX()).isEqualTo(0f);
+ assertThat(meteringPoint2.getNormalizedCropRegionY()).isEqualTo(1f);
+
+ MeteringPoint meteringPoint3 = factory.createPoint(WIDTH, 0f);
+
+ assertThat(meteringPoint3.getNormalizedCropRegionX()).isEqualTo(1f);
+ assertThat(meteringPoint3.getNormalizedCropRegionY()).isEqualTo(0f);
+
+ MeteringPoint meteringPoint4 = factory.createPoint(WIDTH, HEIGHT);
+
+ assertThat(meteringPoint4.getNormalizedCropRegionX()).isEqualTo(1f);
+ assertThat(meteringPoint4.getNormalizedCropRegionY()).isEqualTo(1f);
+ }
+
+ @Test
+ public void display90_front() {
+ when(mMockDisplay.getRotation()).thenReturn(Surface.ROTATION_90);
+
+ DisplayOrientedMeteringPointFactory factory = new DisplayOrientedMeteringPointFactory(
+ mMockContext, CameraX.LensFacing.FRONT, WIDTH, HEIGHT);
+
+ MeteringPoint meteringPoint = factory.createPoint(0f, 0f);
+ assertThat(meteringPoint.getNormalizedCropRegionX()).isEqualTo(1f);
+ assertThat(meteringPoint.getNormalizedCropRegionY()).isEqualTo(0f);
+
+ MeteringPoint meteringPoint2 = factory.createPoint(0f, HEIGHT);
+ assertThat(meteringPoint2.getNormalizedCropRegionX()).isEqualTo(1f);
+ assertThat(meteringPoint2.getNormalizedCropRegionY()).isEqualTo(1f);
+
+ MeteringPoint meteringPoint3 = factory.createPoint(WIDTH, 0f);
+
+ assertThat(meteringPoint3.getNormalizedCropRegionX()).isEqualTo(0f);
+ assertThat(meteringPoint3.getNormalizedCropRegionY()).isEqualTo(0f);
+
+ MeteringPoint meteringPoint4 = factory.createPoint(WIDTH, HEIGHT);
+
+ assertThat(meteringPoint4.getNormalizedCropRegionX()).isEqualTo(0f);
+ assertThat(meteringPoint4.getNormalizedCropRegionY()).isEqualTo(1f);
+ }
+
+ @Test
+ public void display180_back() {
+ when(mMockDisplay.getRotation()).thenReturn(Surface.ROTATION_180);
+
+ DisplayOrientedMeteringPointFactory factory = new DisplayOrientedMeteringPointFactory(
+ mMockContext, CameraX.LensFacing.BACK, WIDTH, HEIGHT);
+
+ MeteringPoint meteringPoint = factory.createPoint(0f, 0f);
+ assertThat(meteringPoint.getNormalizedCropRegionX()).isEqualTo(1f);
+ assertThat(meteringPoint.getNormalizedCropRegionY()).isEqualTo(0f);
+
+ MeteringPoint meteringPoint2 = factory.createPoint(0f, HEIGHT);
+ assertThat(meteringPoint2.getNormalizedCropRegionX()).isEqualTo(0f);
+ assertThat(meteringPoint2.getNormalizedCropRegionY()).isEqualTo(0f);
+
+ MeteringPoint meteringPoint3 = factory.createPoint(WIDTH, 0f);
+
+ assertThat(meteringPoint3.getNormalizedCropRegionX()).isEqualTo(1f);
+ assertThat(meteringPoint3.getNormalizedCropRegionY()).isEqualTo(1f);
+
+ MeteringPoint meteringPoint4 = factory.createPoint(WIDTH, HEIGHT);
+
+ assertThat(meteringPoint4.getNormalizedCropRegionX()).isEqualTo(0f);
+ assertThat(meteringPoint4.getNormalizedCropRegionY()).isEqualTo(1f);
+ }
+
+ @Test
+ public void display180_front() {
+ when(mMockDisplay.getRotation()).thenReturn(Surface.ROTATION_180);
+
+ DisplayOrientedMeteringPointFactory factory = new DisplayOrientedMeteringPointFactory(
+ mMockContext, CameraX.LensFacing.FRONT, WIDTH, HEIGHT);
+
+ MeteringPoint meteringPoint = factory.createPoint(0f, 0f);
+ assertThat(meteringPoint.getNormalizedCropRegionX()).isEqualTo(0f);
+ assertThat(meteringPoint.getNormalizedCropRegionY()).isEqualTo(0f);
+
+ MeteringPoint meteringPoint2 = factory.createPoint(0f, HEIGHT);
+ assertThat(meteringPoint2.getNormalizedCropRegionX()).isEqualTo(1f);
+ assertThat(meteringPoint2.getNormalizedCropRegionY()).isEqualTo(0f);
+
+ MeteringPoint meteringPoint3 = factory.createPoint(WIDTH, 0f);
+
+ assertThat(meteringPoint3.getNormalizedCropRegionX()).isEqualTo(0f);
+ assertThat(meteringPoint3.getNormalizedCropRegionY()).isEqualTo(1f);
+
+ MeteringPoint meteringPoint4 = factory.createPoint(WIDTH, HEIGHT);
+
+ assertThat(meteringPoint4.getNormalizedCropRegionX()).isEqualTo(1f);
+ assertThat(meteringPoint4.getNormalizedCropRegionY()).isEqualTo(1f);
+ }
+
+ @Test
+ public void display270_back() {
+ when(mMockDisplay.getRotation()).thenReturn(Surface.ROTATION_270);
+
+ DisplayOrientedMeteringPointFactory factory = new DisplayOrientedMeteringPointFactory(
+ mMockContext, CameraX.LensFacing.BACK, WIDTH, HEIGHT);
+
+ MeteringPoint meteringPoint = factory.createPoint(0f, 0f);
+ assertThat(meteringPoint.getNormalizedCropRegionX()).isEqualTo(1f);
+ assertThat(meteringPoint.getNormalizedCropRegionY()).isEqualTo(1f);
+
+ MeteringPoint meteringPoint2 = factory.createPoint(0f, HEIGHT);
+ assertThat(meteringPoint2.getNormalizedCropRegionX()).isEqualTo(1f);
+ assertThat(meteringPoint2.getNormalizedCropRegionY()).isEqualTo(0f);
+
+ MeteringPoint meteringPoint3 = factory.createPoint(WIDTH, 0f);
+
+ assertThat(meteringPoint3.getNormalizedCropRegionX()).isEqualTo(0f);
+ assertThat(meteringPoint3.getNormalizedCropRegionY()).isEqualTo(1f);
+
+ MeteringPoint meteringPoint4 = factory.createPoint(WIDTH, HEIGHT);
+
+ assertThat(meteringPoint4.getNormalizedCropRegionX()).isEqualTo(0f);
+ assertThat(meteringPoint4.getNormalizedCropRegionY()).isEqualTo(0f);
+ }
+
+ @Test
+ public void display270_front() {
+ when(mMockDisplay.getRotation()).thenReturn(Surface.ROTATION_270);
+
+ DisplayOrientedMeteringPointFactory factory = new DisplayOrientedMeteringPointFactory(
+ mMockContext, CameraX.LensFacing.FRONT, WIDTH, HEIGHT);
+
+ MeteringPoint meteringPoint = factory.createPoint(0f, 0f);
+ assertThat(meteringPoint.getNormalizedCropRegionX()).isEqualTo(0f);
+ assertThat(meteringPoint.getNormalizedCropRegionY()).isEqualTo(1f);
+
+ MeteringPoint meteringPoint2 = factory.createPoint(0f, HEIGHT);
+ assertThat(meteringPoint2.getNormalizedCropRegionX()).isEqualTo(0f);
+ assertThat(meteringPoint2.getNormalizedCropRegionY()).isEqualTo(0f);
+
+ MeteringPoint meteringPoint3 = factory.createPoint(WIDTH, 0f);
+
+ assertThat(meteringPoint3.getNormalizedCropRegionX()).isEqualTo(1f);
+ assertThat(meteringPoint3.getNormalizedCropRegionY()).isEqualTo(1f);
+
+ MeteringPoint meteringPoint4 = factory.createPoint(WIDTH, HEIGHT);
+
+ assertThat(meteringPoint4.getNormalizedCropRegionX()).isEqualTo(1f);
+ assertThat(meteringPoint4.getNormalizedCropRegionY()).isEqualTo(0f);
+ }
+
+ @Test
+ public void display0_back_useCustomDisplay() {
+ when(mMockDisplay.getRotation()).thenReturn(Surface.ROTATION_270);
+
+ DisplayOrientedMeteringPointFactory factory = new DisplayOrientedMeteringPointFactory(
+ mMockDisplay, CameraX.LensFacing.FRONT, WIDTH, HEIGHT);
+
+ MeteringPoint meteringPoint = factory.createPoint(0f, 0f);
+ assertThat(meteringPoint.getNormalizedCropRegionX()).isEqualTo(0f);
+ assertThat(meteringPoint.getNormalizedCropRegionY()).isEqualTo(1f);
+
+ MeteringPoint meteringPoint2 = factory.createPoint(0f, HEIGHT);
+ assertThat(meteringPoint2.getNormalizedCropRegionX()).isEqualTo(0f);
+ assertThat(meteringPoint2.getNormalizedCropRegionY()).isEqualTo(0f);
+
+ MeteringPoint meteringPoint3 = factory.createPoint(WIDTH, 0f);
+
+ assertThat(meteringPoint3.getNormalizedCropRegionX()).isEqualTo(1f);
+ assertThat(meteringPoint3.getNormalizedCropRegionY()).isEqualTo(1f);
+
+ MeteringPoint meteringPoint4 = factory.createPoint(WIDTH, HEIGHT);
+
+ assertThat(meteringPoint4.getNormalizedCropRegionX()).isEqualTo(1f);
+ assertThat(meteringPoint4.getNormalizedCropRegionY()).isEqualTo(0f);
+ }
+
+}
diff --git a/camera/camera-camera2/src/test/java/androidx/camera/camera2/impl/FocusMeteringControlTest.java b/camera/camera-camera2/src/test/java/androidx/camera/camera2/impl/FocusMeteringControlTest.java
new file mode 100644
index 0000000..cf2bb81
--- /dev/null
+++ b/camera/camera-camera2/src/test/java/androidx/camera/camera2/impl/FocusMeteringControlTest.java
@@ -0,0 +1,903 @@
+/*
+ * 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.camera.camera2.impl;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyBoolean;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.timeout;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.content.Context;
+import android.graphics.Rect;
+import android.hardware.camera2.CameraAccessException;
+import android.hardware.camera2.CameraCharacteristics;
+import android.hardware.camera2.CameraManager;
+import android.hardware.camera2.CaptureRequest;
+import android.hardware.camera2.CaptureResult;
+import android.hardware.camera2.TotalCaptureResult;
+import android.hardware.camera2.params.MeteringRectangle;
+import android.os.Handler;
+import android.os.Looper;
+import android.util.Rational;
+import android.util.Size;
+
+import androidx.annotation.NonNull;
+import androidx.camera.camera2.Camera2AppConfig;
+import androidx.camera.camera2.Camera2Config;
+import androidx.camera.camera2.impl.Camera2CameraControl.CaptureResultListener;
+import androidx.camera.core.AppConfig;
+import androidx.camera.core.CameraX;
+import androidx.camera.core.FocusMeteringAction;
+import androidx.camera.core.ImageAnalysis;
+import androidx.camera.core.MeteringPoint;
+import androidx.camera.core.MeteringPointFactory;
+import androidx.camera.core.Observable;
+import androidx.camera.core.SensorOrientedMeteringPointFactory;
+import androidx.camera.core.impl.utils.executor.CameraXExecutors;
+import androidx.test.core.app.ApplicationProvider;
+import androidx.test.filters.SmallTest;
+
+import com.google.common.collect.Sets;
+import com.google.common.util.concurrent.ListenableFuture;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mockito;
+import org.robolectric.RobolectricTestRunner;
+import org.robolectric.annotation.internal.DoNotInstrument;
+import org.robolectric.shadow.api.Shadow;
+import org.robolectric.shadows.ShadowCameraCharacteristics;
+import org.robolectric.shadows.ShadowCameraManager;
+import org.robolectric.shadows.ShadowLooper;
+
+import java.util.concurrent.Executor;
+import java.util.concurrent.TimeUnit;
+
+@SmallTest
+@RunWith(RobolectricTestRunner.class)
+@DoNotInstrument
+public class FocusMeteringControlTest {
+ private static final String CAMERA0_ID = "0";
+ private static final String CAMERA1_ID = "1";
+ private static final int SENSOR_WIDTH = 640;
+ private static final int SENSOR_HEIGHT = 480;
+ private static final int SENSOR_WIDTH2 = 1920;
+ private static final int SENSOR_HEIGHT2 = 1080;
+
+ private static final int AREA_WIDTH =
+ (int) (MeteringPointFactory.DEFAULT_AREASIZE * SENSOR_WIDTH);
+ private static final int AREA_HEIGHT =
+ (int) (MeteringPointFactory.DEFAULT_AREASIZE * SENSOR_HEIGHT);
+ private static final int AREA_WIDTH2 =
+ (int) (MeteringPointFactory.DEFAULT_AREASIZE * SENSOR_WIDTH2);
+ private static final int AREA_HEIGHT2 =
+ (int) (MeteringPointFactory.DEFAULT_AREASIZE * SENSOR_HEIGHT2);
+
+ SensorOrientedMeteringPointFactory mPointFactory = new SensorOrientedMeteringPointFactory(1, 1);
+ FocusMeteringControl mFocusMeteringControl;
+
+ MeteringPoint mPoint1 = mPointFactory.createPoint(0, 0);
+ MeteringPoint mPoint2 = mPointFactory.createPoint(0.0f, 1.0f);
+ MeteringPoint mPoint3 = mPointFactory.createPoint(1.0f, 1.0f);
+
+ Rect mMRect1 = new Rect(0, 0, AREA_WIDTH / 2, AREA_HEIGHT / 2);
+ Rect mMRect2 = new Rect(0, SENSOR_HEIGHT - AREA_HEIGHT / 2, AREA_WIDTH / 2, SENSOR_HEIGHT);
+ Rect mMRect3 = new Rect(SENSOR_WIDTH - AREA_WIDTH / 2, SENSOR_HEIGHT - AREA_HEIGHT / 2,
+ SENSOR_WIDTH, SENSOR_HEIGHT);
+
+ Rational mPreviewAspectRatio4X3 = new Rational(4, 3);
+ Camera2CameraControl mCamera2CameraControl;
+
+ @Before
+ public void setUp() throws CameraAccessException {
+ Context context = ApplicationProvider.getApplicationContext();
+ AppConfig config = Camera2AppConfig.create(context);
+ CameraX.init(context, config);
+ initCameraManager();
+ mFocusMeteringControl = spy(initFocusMeteringControl(CAMERA0_ID));
+ }
+
+ private FocusMeteringControl initFocusMeteringControl(String cameraID) throws
+ CameraAccessException {
+ CameraManager cameraManager =
+ (CameraManager) ApplicationProvider.getApplicationContext().getSystemService(
+ Context.CAMERA_SERVICE);
+ Camera camera = spy(
+ new Camera(cameraManager, cameraID, getObserable(),
+ new Handler(Looper.getMainLooper())));
+
+ CameraCharacteristics cameraCharacteristics =
+ cameraManager.getCameraCharacteristics(
+ cameraID);
+
+ mCamera2CameraControl = spy(new Camera2CameraControl(cameraCharacteristics, camera,
+ CameraXExecutors.mainThreadExecutor(), CameraXExecutors.mainThreadExecutor()));
+
+
+ return new FocusMeteringControl(mCamera2CameraControl,
+ CameraXExecutors.mainThreadExecutor(), CameraXExecutors.mainThreadExecutor());
+ }
+
+ private Observable<Integer> getObserable() {
+ return new Observable<Integer>() {
+ @NonNull
+ @Override
+ public ListenableFuture<Integer> fetchData() {
+ return null;
+ }
+
+ @Override
+ public void addObserver(@NonNull Executor executor,
+ @NonNull Observer<Integer> observer) {
+
+ }
+
+ @Override
+ public void removeObserver(@NonNull Observer<Integer> observer) {
+
+ }
+ };
+ }
+
+ private void initCameraManager() {
+ // **** Camera 0 characteristics ****//
+ CameraCharacteristics characteristics0 =
+ ShadowCameraCharacteristics.newCameraCharacteristics();
+
+ ShadowCameraCharacteristics shadowCharacteristics0 = Shadow.extract(characteristics0);
+
+ shadowCharacteristics0.set(CameraCharacteristics.SENSOR_INFO_ACTIVE_ARRAY_SIZE,
+ new Rect(0, 0, SENSOR_WIDTH, SENSOR_HEIGHT));
+ shadowCharacteristics0.set(CameraCharacteristics.CONTROL_AF_AVAILABLE_MODES, new int[] {
+ CaptureResult.CONTROL_AF_MODE_CONTINUOUS_PICTURE,
+ CaptureResult.CONTROL_AF_MODE_AUTO,
+ CaptureResult.CONTROL_AF_MODE_OFF
+ });
+ shadowCharacteristics0.set(CameraCharacteristics.CONTROL_AE_AVAILABLE_MODES, new int[] {
+ CaptureResult.CONTROL_AE_MODE_ON,
+ CaptureResult.CONTROL_AE_MODE_ON_ALWAYS_FLASH,
+ CaptureResult.CONTROL_AE_MODE_ON_AUTO_FLASH,
+ CaptureResult.CONTROL_AE_MODE_OFF
+ });
+ shadowCharacteristics0.set(CameraCharacteristics.CONTROL_AWB_AVAILABLE_MODES, new int[] {
+ CaptureResult.CONTROL_AWB_MODE_AUTO,
+ CaptureResult.CONTROL_AWB_MODE_OFF,
+ });
+
+ // Add the camera to the camera service
+ ((ShadowCameraManager)
+ Shadow.extract(
+ ApplicationProvider.getApplicationContext()
+ .getSystemService(Context.CAMERA_SERVICE)))
+ .addCamera(CAMERA0_ID, characteristics0);
+
+ // **** Camera 0 characteristics ****//
+ CameraCharacteristics characteristics1 =
+ ShadowCameraCharacteristics.newCameraCharacteristics();
+
+ ShadowCameraCharacteristics shadowCharacteristics1 = Shadow.extract(characteristics1);
+
+ shadowCharacteristics1.set(CameraCharacteristics.SENSOR_INFO_ACTIVE_ARRAY_SIZE,
+ new Rect(0, 0, SENSOR_WIDTH2, SENSOR_HEIGHT2));
+ // Add the camera to the camera service
+ ((ShadowCameraManager)
+ Shadow.extract(
+ ApplicationProvider.getApplicationContext()
+ .getSystemService(Context.CAMERA_SERVICE)))
+ .addCamera(CAMERA1_ID, characteristics1);
+
+ }
+
+ private MeteringRectangle[] getAfRects(FocusMeteringControl control) {
+ Camera2Config.Builder configBuilder = new Camera2Config.Builder();
+ control.addFocusMeteringOptions(configBuilder);
+ Camera2Config config = configBuilder.build();
+
+ return config.getCaptureRequestOption(CaptureRequest.CONTROL_AF_REGIONS,
+ new MeteringRectangle[]{});
+ }
+
+ private MeteringRectangle[] getAeRects(FocusMeteringControl control) {
+ Camera2Config.Builder configBuilder = new Camera2Config.Builder();
+ control.addFocusMeteringOptions(configBuilder);
+ Camera2Config config = configBuilder.build();
+
+ return config.getCaptureRequestOption(CaptureRequest.CONTROL_AE_REGIONS,
+ new MeteringRectangle[]{});
+ }
+
+ private MeteringRectangle[] getAwbRects(FocusMeteringControl control) {
+ Camera2Config.Builder configBuilder = new Camera2Config.Builder();
+ control.addFocusMeteringOptions(configBuilder);
+ Camera2Config config = configBuilder.build();
+
+ return config.getCaptureRequestOption(CaptureRequest.CONTROL_AWB_REGIONS,
+ new MeteringRectangle[]{});
+ }
+
+ @Test
+ public void startFocusAndMetering_defaultPoint_3ARectssAreCorrect() {
+ mFocusMeteringControl.startFocusAndMetering(
+ FocusMeteringAction.Builder.from(mPoint1).build(),
+ mPreviewAspectRatio4X3);
+
+ MeteringRectangle[] afRects = getAfRects(mFocusMeteringControl);
+ MeteringRectangle[] aeRects = getAeRects(mFocusMeteringControl);
+ MeteringRectangle[] awbRects = getAwbRects(mFocusMeteringControl);
+
+ assertThat(afRects.length).isEqualTo(1);
+ assertThat(afRects[0].getRect()).isEqualTo(mMRect1);
+ assertThat(aeRects.length).isEqualTo(1);
+ assertThat(aeRects[0].getRect()).isEqualTo(mMRect1);
+ assertThat(awbRects.length).isEqualTo(1);
+ assertThat(awbRects[0].getRect()).isEqualTo(mMRect1);
+ }
+
+ @Test
+ public void startFocusAndMetering_multiplePoint_3ARectsAreCorrect() {
+ mFocusMeteringControl.startFocusAndMetering(
+ FocusMeteringAction.Builder.from(mPoint1)
+ .addPoint(mPoint2)
+ .addPoint(mPoint3)
+ .build(), mPreviewAspectRatio4X3);
+
+ MeteringRectangle[] afRects = getAfRects(mFocusMeteringControl);
+ MeteringRectangle[] aeRects = getAeRects(mFocusMeteringControl);
+ MeteringRectangle[] awbRects = getAwbRects(mFocusMeteringControl);
+
+ assertThat(afRects.length).isEqualTo(3);
+ assertThat(afRects[0].getRect()).isEqualTo(mMRect1);
+ assertThat(afRects[1].getRect()).isEqualTo(mMRect2);
+ assertThat(afRects[2].getRect()).isEqualTo(mMRect3);
+
+ assertThat(aeRects.length).isEqualTo(3);
+ assertThat(aeRects[0].getRect()).isEqualTo(mMRect1);
+ assertThat(aeRects[1].getRect()).isEqualTo(mMRect2);
+ assertThat(aeRects[2].getRect()).isEqualTo(mMRect3);
+
+ assertThat(awbRects.length).isEqualTo(3);
+ assertThat(awbRects[0].getRect()).isEqualTo(mMRect1);
+ assertThat(awbRects[1].getRect()).isEqualTo(mMRect2);
+ assertThat(awbRects[2].getRect()).isEqualTo(mMRect3);
+ }
+
+ @Test
+ public void startFocusAndMetering_multiplePointVariousModes() {
+ mFocusMeteringControl.startFocusAndMetering(
+ FocusMeteringAction.Builder.from(mPoint1, FocusMeteringAction.MeteringMode.AWB_ONLY)
+ .addPoint(mPoint2, FocusMeteringAction.MeteringMode.AF_AE)
+ .addPoint(mPoint3, FocusMeteringAction.MeteringMode.AF_AE_AWB)
+ .build(), mPreviewAspectRatio4X3);
+
+ MeteringRectangle[] afRects = getAfRects(mFocusMeteringControl);
+ MeteringRectangle[] aeRects = getAeRects(mFocusMeteringControl);
+ MeteringRectangle[] awbRects = getAwbRects(mFocusMeteringControl);
+
+ assertThat(afRects.length).isEqualTo(2);
+ assertThat(afRects[0].getRect()).isEqualTo(mMRect2);
+ assertThat(afRects[1].getRect()).isEqualTo(mMRect3);
+
+ assertThat(aeRects.length).isEqualTo(2);
+ assertThat(aeRects[0].getRect()).isEqualTo(mMRect2);
+ assertThat(aeRects[1].getRect()).isEqualTo(mMRect3);
+
+ assertThat(awbRects.length).isEqualTo(2);
+ assertThat(awbRects[0].getRect()).isEqualTo(mMRect1);
+ assertThat(awbRects[1].getRect()).isEqualTo(mMRect3);
+ }
+
+ @Test
+ public void startFocusAndMetering_multiplePointVariousModes2() {
+ mFocusMeteringControl.startFocusAndMetering(
+ FocusMeteringAction.Builder.from(mPoint1, FocusMeteringAction.MeteringMode.AF_ONLY)
+ .addPoint(mPoint2, FocusMeteringAction.MeteringMode.AWB_ONLY)
+ .addPoint(mPoint3, FocusMeteringAction.MeteringMode.AE_ONLY)
+ .build(), mPreviewAspectRatio4X3);
+ MeteringRectangle[] afRects = getAfRects(mFocusMeteringControl);
+ MeteringRectangle[] aeRects = getAeRects(mFocusMeteringControl);
+ MeteringRectangle[] awbRects = getAwbRects(mFocusMeteringControl);
+
+ assertThat(afRects.length).isEqualTo(1);
+ assertThat(afRects[0].getRect()).isEqualTo(mMRect1);
+
+ assertThat(aeRects.length).isEqualTo(1);
+ assertThat(aeRects[0].getRect()).isEqualTo(mMRect3);
+
+ assertThat(awbRects.length).isEqualTo(1);
+ assertThat(awbRects[0].getRect()).isEqualTo(mMRect2);
+ }
+
+ @Test
+ public void cropRegionIsSet_resultBasedOnCropRegion() {
+ final int cropWidth = 480;
+ final int cropHeight = 360;
+ Rect cropRect = new Rect(SENSOR_WIDTH / 2 - cropWidth / 2,
+ SENSOR_HEIGHT / 2 - cropHeight / 2,
+ SENSOR_WIDTH / 2 + cropWidth / 2, SENSOR_HEIGHT / 2 + cropHeight / 2);
+ when(mCamera2CameraControl.getCropSensorRegion()).thenReturn(cropRect);
+
+ MeteringPoint centorPt = mPointFactory.createPoint(0.5f, 0.5f);
+ mFocusMeteringControl.startFocusAndMetering(
+ FocusMeteringAction.Builder.from(centorPt).build(),
+ mPreviewAspectRatio4X3);
+ MeteringRectangle[] afRects = getAfRects(mFocusMeteringControl);
+
+ final int areaWidth = (int) (MeteringPointFactory.DEFAULT_AREASIZE * cropRect.width());
+ final int areaHeight = (int) (MeteringPointFactory.DEFAULT_AREASIZE * cropRect.height());
+ Rect adjustedRect = new Rect(cropRect.centerX() - areaWidth / 2,
+ cropRect.centerY() - areaHeight / 2,
+ cropRect.centerX() + areaWidth / 2,
+ cropRect.centerY() + areaHeight / 2);
+
+ assertThat(afRects[0].getRect()).isEqualTo(adjustedRect);
+ }
+
+ @Test
+ public void previewFovAdjusted_16by9_to_4by3() {
+ // use 16:9 preview aspect ratio
+ Rational previewAspectRatio = new Rational(16, 9);
+ mFocusMeteringControl.startFocusAndMetering(
+ FocusMeteringAction.Builder.from(mPoint1).build(),
+ previewAspectRatio);
+ MeteringRectangle[] afRects = getAfRects(mFocusMeteringControl);
+
+
+ Rect adjustedRect = new Rect(0, 60 - AREA_HEIGHT / 2, AREA_WIDTH / 2, 60 + AREA_HEIGHT / 2);
+ assertThat(afRects[0].getRect()).isEqualTo(adjustedRect);
+ }
+
+ @Test
+ public void previewFovAdjusted_4by3_to_16by9()
+ throws CameraAccessException {
+ //Camera1 sensor region is 16:9
+ mFocusMeteringControl = initFocusMeteringControl(CAMERA1_ID);
+
+ mFocusMeteringControl.startFocusAndMetering(
+ FocusMeteringAction.Builder.from(mPoint1).build(),
+ mPreviewAspectRatio4X3);
+ MeteringRectangle[] afRects = getAfRects(mFocusMeteringControl);
+
+ Rect adjustedRect = new Rect(240 - AREA_WIDTH2 / 2, 0, 240 + AREA_WIDTH2 / 2,
+ AREA_HEIGHT2 / 2);
+ assertThat(afRects[0].getRect()).isEqualTo(adjustedRect);
+ }
+
+ @Test
+ public void customFovAdjusted() {
+ // 16:9 to 4:3
+ ImageAnalysis imageAnalysis = Mockito.mock(ImageAnalysis.class);
+ when(imageAnalysis.getAttachedSurfaceResolution(any())).thenReturn(
+ new Size(1920, 1080));
+ when(imageAnalysis.getAttachedCameraIds()).thenReturn(Sets.newHashSet(CAMERA0_ID));
+
+ SensorOrientedMeteringPointFactory factory =
+ new SensorOrientedMeteringPointFactory(1.0f, 1.0f, imageAnalysis);
+
+ MeteringPoint point = factory.createPoint(0, 0);
+ mFocusMeteringControl.startFocusAndMetering(FocusMeteringAction.Builder.from(point).build(),
+ mPreviewAspectRatio4X3);
+ MeteringRectangle[] afRects = getAfRects(mFocusMeteringControl);
+
+
+ Rect adjustedRect = new Rect(0, 60 - AREA_HEIGHT / 2, AREA_WIDTH / 2, 60 + AREA_HEIGHT / 2);
+ assertThat(afRects[0].getRect()).isEqualTo(adjustedRect);
+ }
+
+ @Test
+ public void weight_ConvertedCorrect() {
+ MeteringPoint point1 = mPointFactory.createPoint(0, 0, 0.2f, 1.0f);
+ MeteringPoint point2 = mPointFactory.createPoint(0, 0, 0.2f, 0.5f);
+ MeteringPoint point3 = mPointFactory.createPoint(0, 0, 0.2f, 0.1f);
+
+ mFocusMeteringControl.startFocusAndMetering(FocusMeteringAction.Builder.from(point1)
+ .addPoint(point2)
+ .addPoint(point3).build(), mPreviewAspectRatio4X3);
+ MeteringRectangle[] afRects = getAfRects(mFocusMeteringControl);
+
+ assertThat(afRects.length).isEqualTo(3);
+ assertThat(afRects[0].getMeteringWeight()).isEqualTo(
+ (int) (MeteringRectangle.METERING_WEIGHT_MAX * 1.0f));
+ assertThat(afRects[1].getMeteringWeight()).isEqualTo(
+ (int) (MeteringRectangle.METERING_WEIGHT_MAX * 0.5f));
+ assertThat(afRects[2].getMeteringWeight()).isEqualTo(
+ (int) (MeteringRectangle.METERING_WEIGHT_MAX * 0.1f));
+ }
+
+ @Test
+ public void invalidWeight_ConvertedCorrect() {
+ MeteringPoint point1 = mPointFactory.createPoint(0, 0, 0.2f, 1.1f);
+ MeteringPoint point2 = mPointFactory.createPoint(0, 0, 0.2f, -0.3f);
+ MeteringPoint point3 = mPointFactory.createPoint(0, 0, 0.2f, 90000f);
+
+ mFocusMeteringControl.startFocusAndMetering(FocusMeteringAction.Builder.from(point1)
+ .addPoint(point2)
+ .addPoint(point3).build(), mPreviewAspectRatio4X3);
+ MeteringRectangle[] afRects = getAfRects(mFocusMeteringControl);
+
+ assertThat(afRects.length).isEqualTo(3);
+ assertThat(afRects[0].getMeteringWeight()).isEqualTo(
+ MeteringRectangle.METERING_WEIGHT_MAX);
+ assertThat(afRects[1].getMeteringWeight()).isEqualTo(
+ MeteringRectangle.METERING_WEIGHT_MIN);
+ assertThat(afRects[2].getMeteringWeight()).isEqualTo(
+ MeteringRectangle.METERING_WEIGHT_MAX);
+ }
+
+ @Test
+ public void pointSize_ConvertedCorrect() {
+ MeteringPoint point1 = mPointFactory.createPoint(0.5f, 0.5f, 1.0f, 1.0f);
+ MeteringPoint point2 = mPointFactory.createPoint(0.5f, 0.5f, 0.5f, 1.0f);
+ MeteringPoint point3 = mPointFactory.createPoint(0.5f, 0.5f, 0.1f, 1.0f);
+
+ mFocusMeteringControl.startFocusAndMetering(FocusMeteringAction.Builder.from(point1)
+ .addPoint(point2)
+ .addPoint(point3).build(), mPreviewAspectRatio4X3);
+ MeteringRectangle[] afRects = getAfRects(mFocusMeteringControl);
+
+ assertThat(afRects.length).isEqualTo(3);
+ assertThat(afRects[0].getWidth()).isEqualTo(
+ (int) (SENSOR_WIDTH * 1.0f));
+ assertThat(afRects[0].getHeight()).isEqualTo(
+ (int) (SENSOR_HEIGHT * 1.0f));
+
+ assertThat(afRects[1].getWidth()).isEqualTo(
+ (int) (SENSOR_WIDTH * 0.5f));
+ assertThat(afRects[1].getHeight()).isEqualTo(
+ (int) (SENSOR_HEIGHT * 0.5f));
+
+ assertThat(afRects[2].getWidth()).isEqualTo(
+ (int) (SENSOR_WIDTH * 0.1f));
+ assertThat(afRects[2].getHeight()).isEqualTo(
+ (int) (SENSOR_HEIGHT * 0.1f));
+ }
+
+ @Test
+ public void withAFPoints_AFIsTriggered() {
+ mFocusMeteringControl.startFocusAndMetering(FocusMeteringAction.Builder.from(mPoint1,
+ FocusMeteringAction.MeteringMode.AF_AE_AWB).build(), mPreviewAspectRatio4X3);
+
+ verify(mFocusMeteringControl).triggerAf();
+ Mockito.reset(mFocusMeteringControl);
+
+ mFocusMeteringControl.startFocusAndMetering(
+ FocusMeteringAction.Builder.from(mPoint1,
+ FocusMeteringAction.MeteringMode.AF_ONLY).build(),
+ mPreviewAspectRatio4X3);
+ verify(mFocusMeteringControl).triggerAf();
+ Mockito.reset(mFocusMeteringControl);
+
+ mFocusMeteringControl.startFocusAndMetering(
+ FocusMeteringAction.Builder.from(mPoint1,
+ FocusMeteringAction.MeteringMode.AF_AE).build(),
+ mPreviewAspectRatio4X3);
+ verify(mFocusMeteringControl).triggerAf();
+ Mockito.reset(mFocusMeteringControl);
+
+ mFocusMeteringControl.startFocusAndMetering(
+ FocusMeteringAction.Builder.from(mPoint1,
+ FocusMeteringAction.MeteringMode.AF_AWB).build(),
+ mPreviewAspectRatio4X3);
+ verify(mFocusMeteringControl).triggerAf();
+ Mockito.reset(mFocusMeteringControl);
+ }
+
+ @Test
+ public void withoutAFPoints_AFIsNotTriggered() {
+ mFocusMeteringControl.startFocusAndMetering(FocusMeteringAction.Builder.from(mPoint1,
+ FocusMeteringAction.MeteringMode.AE_ONLY).build(), mPreviewAspectRatio4X3);
+ verify(mFocusMeteringControl, never()).triggerAf();
+ Mockito.reset(mFocusMeteringControl);
+
+ mFocusMeteringControl.startFocusAndMetering(
+ FocusMeteringAction.Builder.from(mPoint1,
+ FocusMeteringAction.MeteringMode.AWB_ONLY).build(),
+ mPreviewAspectRatio4X3);
+ verify(mFocusMeteringControl, never()).triggerAf();
+ Mockito.reset(mFocusMeteringControl);
+
+ mFocusMeteringControl.startFocusAndMetering(
+ FocusMeteringAction.Builder.from(mPoint1,
+ FocusMeteringAction.MeteringMode.AE_ONLY).build(),
+ mPreviewAspectRatio4X3);
+ verify(mFocusMeteringControl, never()).triggerAf();
+ Mockito.reset(mFocusMeteringControl);
+
+ mFocusMeteringControl.startFocusAndMetering(
+ FocusMeteringAction.Builder.from(mPoint1,
+ FocusMeteringAction.MeteringMode.AE_AWB).build(),
+ mPreviewAspectRatio4X3);
+ verify(mFocusMeteringControl, never()).triggerAf();
+ Mockito.reset(mFocusMeteringControl);
+ }
+
+ @Test
+ public void updateSessionConfigIsCalled() {
+ mFocusMeteringControl.startFocusAndMetering(
+ FocusMeteringAction.Builder.from(mPoint1).build(),
+ mPreviewAspectRatio4X3);
+
+ verify(mCamera2CameraControl, times(1)).updateSessionConfig();
+ }
+
+ @Test
+ public void autoCancelDuration_cancelIsCalled() throws InterruptedException {
+ mFocusMeteringControl = spy(mFocusMeteringControl);
+ final long autocancelDuration = 500;
+ FocusMeteringAction action = FocusMeteringAction.Builder.from(mPoint1)
+ .setAutoCancelDuration(autocancelDuration, TimeUnit.MILLISECONDS)
+ .build();
+
+ mFocusMeteringControl.startFocusAndMetering(action, mPreviewAspectRatio4X3);
+
+ // This is necessary for running delayed task in robolectric.
+ ShadowLooper.runUiThreadTasksIncludingDelayedTasks();
+ verify(mFocusMeteringControl, timeout(action.getAutoCancelDurationInMs()))
+ .cancelFocusAndMetering();
+ }
+
+ @Test
+ public void autoCancelDurationDisabled_cancelIsNotCalled() throws InterruptedException {
+ mFocusMeteringControl = spy(mFocusMeteringControl);
+ final long autocancelDuration = 500;
+
+ FocusMeteringAction action = FocusMeteringAction.Builder.from(mPoint1)
+ .setAutoCancelDuration(autocancelDuration, TimeUnit.MILLISECONDS)
+ .disableAutoCancel()
+ .build();
+
+ mFocusMeteringControl.startFocusAndMetering(action, mPreviewAspectRatio4X3);
+
+ // This is necessary for running delayed task in robolectric.
+ ShadowLooper.runUiThreadTasksIncludingDelayedTasks();
+ Thread.sleep(autocancelDuration);
+ verify(mFocusMeteringControl, never()).cancelFocusAndMetering();
+ }
+
+ @Test
+ public void onAutoFocusListener_cancelImmediately_calledWithFalse() {
+ FocusMeteringAction.OnAutoFocusListener onAutoFocusListener = mock(
+ FocusMeteringAction.OnAutoFocusListener.class);
+
+ FocusMeteringAction action = FocusMeteringAction.Builder.from(mPoint1)
+ .setAutoFocusCallback(onAutoFocusListener)
+ .build();
+ mFocusMeteringControl.startFocusAndMetering(action, mPreviewAspectRatio4X3);
+ mFocusMeteringControl.cancelFocusAndMetering();
+
+ verify(onAutoFocusListener).onFocusCompleted(false);
+ }
+
+ @Test
+ public void onAutoFocusListener_AFNotTriggered_calledWithFalse() {
+ FocusMeteringAction.OnAutoFocusListener onAutoFocusListener = mock(
+ FocusMeteringAction.OnAutoFocusListener.class);
+
+ FocusMeteringAction action = FocusMeteringAction.Builder.from(mPoint1,
+ FocusMeteringAction.MeteringMode.AE_AWB)
+ .setAutoFocusCallback(onAutoFocusListener)
+ .build();
+ mFocusMeteringControl.startFocusAndMetering(action, mPreviewAspectRatio4X3);
+
+ verify(onAutoFocusListener).onFocusCompleted(false);
+ Mockito.reset(onAutoFocusListener);
+
+ action = FocusMeteringAction.Builder.from(mPoint1,
+ FocusMeteringAction.MeteringMode.AE_ONLY)
+ .setAutoFocusCallback(onAutoFocusListener)
+ .build();
+ mFocusMeteringControl.startFocusAndMetering(action, mPreviewAspectRatio4X3);
+ verify(onAutoFocusListener).onFocusCompleted(false);
+ Mockito.reset(onAutoFocusListener);
+
+ action = FocusMeteringAction.Builder.from(mPoint1,
+ FocusMeteringAction.MeteringMode.AWB_ONLY)
+ .setAutoFocusCallback(onAutoFocusListener)
+ .build();
+ mFocusMeteringControl.startFocusAndMetering(action, mPreviewAspectRatio4X3);
+ verify(onAutoFocusListener).onFocusCompleted(false);
+ Mockito.reset(onAutoFocusListener);
+ }
+
+ @Test
+ public void onAutoFocusListener_AFLocked_calledWithTrue() {
+ FocusMeteringAction.OnAutoFocusListener onAutoFocusListener = mock(
+ FocusMeteringAction.OnAutoFocusListener.class);
+
+ FocusMeteringAction action = FocusMeteringAction.Builder.from(mPoint1)
+ .setAutoFocusCallback(onAutoFocusListener)
+ .build();
+
+ mFocusMeteringControl.startFocusAndMetering(action, mPreviewAspectRatio4X3);
+
+ for (CaptureResultListener listener :
+ mCamera2CameraControl.mSessionCallback.mResultListeners) {
+ TotalCaptureResult result1 = mock(TotalCaptureResult.class);
+ when(result1.get(CaptureResult.CONTROL_AF_STATE)).thenReturn(
+ CaptureResult.CONTROL_AF_STATE_ACTIVE_SCAN);
+ listener.onCaptureResult(result1);
+
+ TotalCaptureResult result2 = mock(TotalCaptureResult.class);
+ when(result2.get(CaptureResult.CONTROL_AF_STATE)).thenReturn(
+ CaptureResult.CONTROL_AF_STATE_FOCUSED_LOCKED);
+ listener.onCaptureResult(result2);
+
+ }
+ verify(onAutoFocusListener).onFocusCompleted(true);
+ }
+
+ @Test
+ public void onAutoFocusListener_executorSpecified_calledWithTheExecutor() {
+ FocusMeteringAction.OnAutoFocusListener onAutoFocusListener = mock(
+ FocusMeteringAction.OnAutoFocusListener.class);
+
+ Executor executor = spy(new Executor() {
+ @Override
+ public void execute(@NonNull Runnable runnable) {
+ runnable.run();
+ }
+ });
+
+ FocusMeteringAction action = FocusMeteringAction.Builder.from(mPoint1)
+ .setAutoFocusCallback(executor, onAutoFocusListener)
+ .build();
+
+ mFocusMeteringControl.startFocusAndMetering(action, mPreviewAspectRatio4X3);
+ for (CaptureResultListener listener :
+ mCamera2CameraControl.mSessionCallback.mResultListeners) {
+ TotalCaptureResult result1 = mock(TotalCaptureResult.class);
+ when(result1.get(CaptureResult.CONTROL_AF_STATE)).thenReturn(
+ CaptureResult.CONTROL_AF_STATE_ACTIVE_SCAN);
+ listener.onCaptureResult(result1);
+
+ TotalCaptureResult result2 = mock(TotalCaptureResult.class);
+ when(result2.get(CaptureResult.CONTROL_AF_STATE)).thenReturn(
+ CaptureResult.CONTROL_AF_STATE_FOCUSED_LOCKED);
+ listener.onCaptureResult(result2);
+
+ }
+
+ verify(onAutoFocusListener).onFocusCompleted(true);
+ verify(executor).execute(any());
+ }
+
+ @Test
+ public void onAutoFocusListener_NotAFLocked_calledWithFalse() {
+ FocusMeteringAction.OnAutoFocusListener onAutoFocusListener = mock(
+ FocusMeteringAction.OnAutoFocusListener.class);
+
+ FocusMeteringAction action = FocusMeteringAction.Builder.from(mPoint1)
+ .setAutoFocusCallback(onAutoFocusListener)
+ .build();
+
+ mFocusMeteringControl.startFocusAndMetering(action, mPreviewAspectRatio4X3);
+
+ for (CaptureResultListener listener :
+ mCamera2CameraControl.mSessionCallback.mResultListeners) {
+ TotalCaptureResult result1 = mock(TotalCaptureResult.class);
+ when(result1.get(CaptureResult.CONTROL_AF_STATE)).thenReturn(
+ CaptureResult.CONTROL_AF_STATE_ACTIVE_SCAN);
+ listener.onCaptureResult(result1);
+
+ TotalCaptureResult result2 = mock(TotalCaptureResult.class);
+ when(result2.get(CaptureResult.CONTROL_AF_STATE)).thenReturn(
+ CaptureResult.CONTROL_AF_STATE_NOT_FOCUSED_LOCKED);
+ listener.onCaptureResult(result2);
+
+ }
+ verify(onAutoFocusListener).onFocusCompleted(false);
+ }
+
+ @Test
+ public void onAutoFocusListener_isCalledOnce() {
+ FocusMeteringAction.OnAutoFocusListener onAutoFocusListener = mock(
+ FocusMeteringAction.OnAutoFocusListener.class);
+
+ FocusMeteringAction action = FocusMeteringAction.Builder.from(mPoint1)
+ .setAutoFocusCallback(onAutoFocusListener)
+ .build();
+
+ mFocusMeteringControl.startFocusAndMetering(action, mPreviewAspectRatio4X3);
+
+ for (CaptureResultListener listener :
+ mCamera2CameraControl.mSessionCallback.mResultListeners) {
+ TotalCaptureResult result1 = mock(TotalCaptureResult.class);
+ when(result1.get(CaptureResult.CONTROL_AF_STATE)).thenReturn(
+ CaptureResult.CONTROL_AF_STATE_ACTIVE_SCAN);
+ listener.onCaptureResult(result1);
+
+ TotalCaptureResult result2 = mock(TotalCaptureResult.class);
+ when(result2.get(CaptureResult.CONTROL_AF_STATE)).thenReturn(
+ CaptureResult.CONTROL_AF_STATE_FOCUSED_LOCKED);
+ listener.onCaptureResult(result2);
+
+ }
+
+ // cancel it and then ensure the OnAutoFocusListener is still called once.
+ mFocusMeteringControl.cancelFocusAndMetering();
+
+ verify(onAutoFocusListener, times(1)).onFocusCompleted(anyBoolean());
+ }
+
+
+ @Test
+ public void cancelFocusAndMetering_regionIsReset() {
+ FocusMeteringAction action = FocusMeteringAction.Builder.from(mPoint1,
+ FocusMeteringAction.MeteringMode.AF_AE_AWB)
+ .addPoint(mPoint2, FocusMeteringAction.MeteringMode.AF_AE_AWB)
+ .build();
+
+ mFocusMeteringControl.startFocusAndMetering(action, mPreviewAspectRatio4X3);
+ MeteringRectangle[] afRects = getAfRects(mFocusMeteringControl);
+ MeteringRectangle[] aeRects = getAeRects(mFocusMeteringControl);
+ MeteringRectangle[] awbRects = getAwbRects(mFocusMeteringControl);
+
+ assertThat(afRects).hasLength(2);
+ assertThat(aeRects).hasLength(2);
+ assertThat(awbRects).hasLength(2);
+
+ mFocusMeteringControl.cancelFocusAndMetering();
+ afRects = getAfRects(mFocusMeteringControl);
+ aeRects = getAeRects(mFocusMeteringControl);
+ awbRects = getAwbRects(mFocusMeteringControl);
+
+ assertThat(afRects).hasLength(0);
+ assertThat(aeRects).hasLength(0);
+ assertThat(awbRects).hasLength(0);
+ }
+
+
+ @Test
+ public void cancelFocusAndMetering_updateSessionIsCalled() {
+ FocusMeteringAction action = FocusMeteringAction.Builder.from(mPoint1,
+ FocusMeteringAction.MeteringMode.AF_AE_AWB)
+ .addPoint(mPoint2, FocusMeteringAction.MeteringMode.AF_AE_AWB)
+ .build();
+
+ mFocusMeteringControl.startFocusAndMetering(action, mPreviewAspectRatio4X3);
+ Mockito.reset(mCamera2CameraControl);
+
+ mFocusMeteringControl.cancelFocusAndMetering();
+ verify(mCamera2CameraControl, times(1)).updateSessionConfig();
+ }
+
+
+ @Test
+ public void cancelFocusAndMetering_triggerCancelAfProperly() {
+ // If AF is enabled, cancel operation needs to call cancelAfAeTriggerInternal(true, false)
+ FocusMeteringAction action = FocusMeteringAction.Builder.from(mPoint1,
+ FocusMeteringAction.MeteringMode.AF_AE_AWB)
+ .build();
+
+ mFocusMeteringControl.startFocusAndMetering(action, mPreviewAspectRatio4X3);
+ Mockito.reset(mFocusMeteringControl);
+ mFocusMeteringControl.cancelFocusAndMetering();
+ verify(mFocusMeteringControl, times(1)).cancelAfAeTrigger(true, false);
+
+ action = FocusMeteringAction.Builder.from(mPoint1, FocusMeteringAction.MeteringMode.AF_AE)
+ .build();
+ mFocusMeteringControl.startFocusAndMetering(action, mPreviewAspectRatio4X3);
+ Mockito.reset(mFocusMeteringControl);
+ mFocusMeteringControl.cancelFocusAndMetering();
+ verify(mFocusMeteringControl, times(1)).cancelAfAeTrigger(true, false);
+
+ action = FocusMeteringAction.Builder.from(mPoint1, FocusMeteringAction.MeteringMode.AF_AWB)
+ .build();
+ mFocusMeteringControl.startFocusAndMetering(action, mPreviewAspectRatio4X3);
+ Mockito.reset(mFocusMeteringControl);
+ mFocusMeteringControl.cancelFocusAndMetering();
+ verify(mFocusMeteringControl, times(1)).cancelAfAeTrigger(true, false);
+
+ action = FocusMeteringAction.Builder.from(mPoint1, FocusMeteringAction.MeteringMode.AF_ONLY)
+ .build();
+ mFocusMeteringControl.startFocusAndMetering(action, mPreviewAspectRatio4X3);
+ Mockito.reset(mFocusMeteringControl);
+ mFocusMeteringControl.cancelFocusAndMetering();
+ verify(mFocusMeteringControl, times(1)).cancelAfAeTrigger(true, false);
+ }
+
+ @Test
+ public void cancelFocusAndMetering_AFNotInvolved_cancelAfNotTriggered() {
+ FocusMeteringAction action = FocusMeteringAction.Builder.from(mPoint1,
+ FocusMeteringAction.MeteringMode.AE_ONLY)
+ .build();
+
+ mFocusMeteringControl.startFocusAndMetering(action, mPreviewAspectRatio4X3);
+ Mockito.reset(mFocusMeteringControl);
+ mFocusMeteringControl.cancelFocusAndMetering();
+ verify(mFocusMeteringControl, never()).cancelAfAeTrigger(true, false);
+
+ action = FocusMeteringAction.Builder.from(mPoint1,
+ FocusMeteringAction.MeteringMode.AWB_ONLY)
+ .build();
+ mFocusMeteringControl.startFocusAndMetering(action, mPreviewAspectRatio4X3);
+ Mockito.reset(mFocusMeteringControl);
+ mFocusMeteringControl.cancelFocusAndMetering();
+ verify(mFocusMeteringControl, never()).cancelAfAeTrigger(true, false);
+
+ action = FocusMeteringAction.Builder.from(mPoint1, FocusMeteringAction.MeteringMode.AE_AWB)
+ .build();
+ mFocusMeteringControl.startFocusAndMetering(action, mPreviewAspectRatio4X3);
+ Mockito.reset(mFocusMeteringControl);
+ mFocusMeteringControl.cancelFocusAndMetering();
+ verify(mFocusMeteringControl, never()).cancelAfAeTrigger(true, false);
+ }
+
+ @Test
+ public void cancelFocusAndMetering_autoCancelIsDisabled() throws InterruptedException {
+ mFocusMeteringControl = spy(mFocusMeteringControl);
+ final long autocancelDuration = 500;
+
+ FocusMeteringAction action = FocusMeteringAction.Builder.from(mPoint1)
+ .setAutoCancelDuration(autocancelDuration, TimeUnit.MILLISECONDS)
+ .build();
+
+ mFocusMeteringControl.startFocusAndMetering(action, mPreviewAspectRatio4X3);
+ mFocusMeteringControl.cancelFocusAndMetering();
+ Mockito.reset(mFocusMeteringControl);
+
+ // This is necessary for running delayed task in robolectric.
+ ShadowLooper.runUiThreadTasksIncludingDelayedTasks();
+ Thread.sleep(autocancelDuration);
+
+ verify(mFocusMeteringControl, never()).cancelFocusAndMetering();
+ }
+
+ @Test
+ public void startFocusMetering_isAfAutoModeIsTrue() {
+ FocusMeteringAction action = FocusMeteringAction.Builder.from(mPoint1).build();
+
+ verifyAfMode(CaptureResult.CONTROL_AF_MODE_CONTINUOUS_PICTURE);
+
+ mFocusMeteringControl.startFocusAndMetering(action, mPreviewAspectRatio4X3);
+
+ verifyAfMode(CaptureResult.CONTROL_AF_MODE_AUTO);
+ }
+
+ private void verifyAfMode(int expectAfMode) {
+ Camera2Config.Builder builder1 = new Camera2Config.Builder();
+ mFocusMeteringControl.addFocusMeteringOptions(builder1);
+ assertThat(builder1.build().getCaptureRequestOption(CaptureRequest.CONTROL_AF_MODE, null))
+ .isEqualTo(expectAfMode);
+ }
+
+ @Test
+ public void startFocusMetering_AfNotInvolved_isAfAutoModeIsSet() {
+ FocusMeteringAction action = FocusMeteringAction.Builder.from(mPoint1,
+ FocusMeteringAction.MeteringMode.AE_AWB).build();
+
+ verifyAfMode(CaptureResult.CONTROL_AF_MODE_CONTINUOUS_PICTURE);
+ mFocusMeteringControl.startFocusAndMetering(action, mPreviewAspectRatio4X3);
+ verifyAfMode(CaptureResult.CONTROL_AF_MODE_CONTINUOUS_PICTURE);
+ }
+
+ @Test
+ public void startAndThenCancel_isAfAutoModeIsFalse() {
+ FocusMeteringAction action = FocusMeteringAction.Builder.from(mPoint1).build();
+ mFocusMeteringControl.startFocusAndMetering(action, mPreviewAspectRatio4X3);
+ mFocusMeteringControl.cancelFocusAndMetering();
+ verifyAfMode(CaptureResult.CONTROL_AF_MODE_CONTINUOUS_PICTURE);
+ }
+
+}
diff --git a/camera/camera-core/build.gradle b/camera/camera-core/build.gradle
index d792fc0..b111e0d 100644
--- a/camera/camera-core/build.gradle
+++ b/camera/camera-core/build.gradle
@@ -29,7 +29,7 @@
api("androidx.lifecycle:lifecycle-common:2.0.0")
implementation("androidx.exifinterface:exifinterface:1.0.0")
implementation("androidx.annotation:annotation:1.0.0")
- implementation("androidx.core:core:1.1.0-rc01")
+ implementation("androidx.core:core:1.1.0")
implementation("androidx.concurrent:concurrent-futures:1.0.0-alpha03")
implementation(ARCH_LIFECYCLE_LIVEDATA)
diff --git a/camera/camera-core/lint-baseline.xml b/camera/camera-core/lint-baseline.xml
index 427bce4b..71a8477 100644
--- a/camera/camera-core/lint-baseline.xml
+++ b/camera/camera-core/lint-baseline.xml
@@ -3315,28 +3315,6 @@
<issue
id="UnknownNullness"
message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://android.github.io/kotlin-guides/interop.html#nullability-annotations"
- errorLine1=" UseCaseError useCaseError, String message, @Nullable Throwable cause) {"
- errorLine2=" ~~~~~~~~~~~~">
- <location
- file="src/main/java/androidx/camera/core/ImageCapture.java"
- line="1031"
- column="17"/>
- </issue>
-
- <issue
- id="UnknownNullness"
- message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://android.github.io/kotlin-guides/interop.html#nullability-annotations"
- errorLine1=" UseCaseError useCaseError, String message, @Nullable Throwable cause) {"
- errorLine2=" ~~~~~~">
- <location
- file="src/main/java/androidx/camera/core/ImageCapture.java"
- line="1031"
- column="44"/>
- </issue>
-
- <issue
- id="UnknownNullness"
- message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://android.github.io/kotlin-guides/interop.html#nullability-annotations"
errorLine1=" public ImageCaptureConfig getConfig(LensFacing lensFacing) {"
errorLine2=" ~~~~~~~~~~~~~~~~~~">
<location
@@ -7033,28 +7011,6 @@
<issue
id="UnknownNullness"
message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://android.github.io/kotlin-guides/interop.html#nullability-annotations"
- errorLine1=" void onError(UseCaseError useCaseError, String message, @Nullable Throwable cause);"
- errorLine2=" ~~~~~~~~~~~~">
- <location
- file="src/main/java/androidx/camera/core/VideoCapture.java"
- line="865"
- column="22"/>
- </issue>
-
- <issue
- id="UnknownNullness"
- message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://android.github.io/kotlin-guides/interop.html#nullability-annotations"
- errorLine1=" void onError(UseCaseError useCaseError, String message, @Nullable Throwable cause);"
- errorLine2=" ~~~~~~">
- <location
- file="src/main/java/androidx/camera/core/VideoCapture.java"
- line="865"
- column="49"/>
- </issue>
-
- <issue
- id="UnknownNullness"
- message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://android.github.io/kotlin-guides/interop.html#nullability-annotations"
errorLine1=" public VideoCaptureConfig getConfig(LensFacing lensFacing) {"
errorLine2=" ~~~~~~~~~~~~~~~~~~">
<location
diff --git a/camera/camera-core/src/androidTest/java/androidx/camera/core/CameraRepositoryTest.java b/camera/camera-core/src/androidTest/java/androidx/camera/core/CameraRepositoryTest.java
deleted file mode 100644
index 3748fc8..0000000
--- a/camera/camera-core/src/androidTest/java/androidx/camera/core/CameraRepositoryTest.java
+++ /dev/null
@@ -1,64 +0,0 @@
-/*
- * Copyright (C) 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.camera.core;
-
-import static com.google.common.truth.Truth.assertThat;
-
-import androidx.camera.testing.fakes.FakeCameraFactory;
-import androidx.test.ext.junit.runners.AndroidJUnit4;
-import androidx.test.filters.SmallTest;
-
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-import java.util.Set;
-
-@SmallTest
-@RunWith(AndroidJUnit4.class)
-public final class CameraRepositoryTest {
-
- private CameraRepository mCameraRepository;
-
- @Before
- public void setUp() {
- mCameraRepository = new CameraRepository();
- mCameraRepository.init(new FakeCameraFactory());
- }
-
- @Test
- public void cameraIdsCanBeAcquired() {
- Set<String> cameraIds = mCameraRepository.getCameraIds();
-
- assertThat(cameraIds).isNotEmpty();
- }
-
- @Test
- public void cameraCanBeObtainedWithValidId() {
- for (String cameraId : mCameraRepository.getCameraIds()) {
- BaseCamera camera = mCameraRepository.getCamera(cameraId);
-
- assertThat(camera).isNotNull();
- }
- }
-
- @Test(expected = IllegalArgumentException.class)
- public void cameraCannotBeObtainedWithInvalidId() {
- // Should throw IllegalArgumentException
- mCameraRepository.getCamera("no_such_id");
- }
-}
diff --git a/camera/camera-core/src/main/java/androidx/camera/core/CameraControl.java b/camera/camera-core/src/main/java/androidx/camera/core/CameraControl.java
index 6d07f6f..f898844 100644
--- a/camera/camera-core/src/main/java/androidx/camera/core/CameraControl.java
+++ b/camera/camera-core/src/main/java/androidx/camera/core/CameraControl.java
@@ -16,10 +16,33 @@
package androidx.camera.core;
+import androidx.annotation.NonNull;
+import androidx.camera.core.FocusMeteringAction.OnAutoFocusListener;
+
/**
* An interface for controlling camera's zoom, focus and metering across all use cases.
*
* <p>Applications can retrieve the interface via CameraX.getCameraControl.
*/
public interface CameraControl {
+
+ /**
+ * Starts a focus and metering action by the {@link FocusMeteringAction}.
+ *
+ * The {@link FocusMeteringAction} contains the configuration of multiple 3A
+ * {@link MeteringPoint}s, auto-cancel duration and{@link OnAutoFocusListener} to receive the
+ * auto-focus result. Check {@link FocusMeteringAction} for more details.
+ *
+ * @param action the {@link FocusMeteringAction} to be executed.
+ */
+ void startFocusAndMetering(@NonNull FocusMeteringAction action);
+
+ /**
+ * Cancels current {@link FocusMeteringAction}.
+ *
+ * <p>It clears the 3A regions and update current AF mode to CONTINOUS AF (if supported).
+ * If auto-focus does not completes, it will notify the {@link OnAutoFocusListener} with
+ * isFocusLocked set to false.
+ */
+ void cancelFocusAndMetering();
}
diff --git a/camera/camera-core/src/main/java/androidx/camera/core/CameraControlInternal.java b/camera/camera-core/src/main/java/androidx/camera/core/CameraControlInternal.java
index 1e01a70..5ae32da 100644
--- a/camera/camera-core/src/main/java/androidx/camera/core/CameraControlInternal.java
+++ b/camera/camera-core/src/main/java/androidx/camera/core/CameraControlInternal.java
@@ -16,7 +16,6 @@
package androidx.camera.core;
-import android.annotation.SuppressLint;
import android.graphics.Rect;
import androidx.annotation.NonNull;
@@ -25,7 +24,6 @@
import androidx.annotation.RestrictTo.Scope;
import java.util.List;
-import java.util.concurrent.Executor;
/**
* The CameraControlInternal Interface.
@@ -48,37 +46,6 @@
*/
void setCropRegion(@Nullable Rect crop);
- /**
- * Adjusts the camera output according to the properties in some local regions with a callback
- * called once focus scan has completed.
- *
- * <p>The auto-focus (AF), auto-exposure (AE) and auto-whitebalance (AWB) properties will be
- * recalculated from the local regions.
- *
- * @param focus rectangle with dimensions in sensor coordinate frame for focus
- * @param metering rectangle with dimensions in sensor coordinate frame for metering
- * @param executor the executor which will be used to call the listener.
- * @param listener listener for when focus has completed.
- */
- @SuppressLint("LambdaLast")
- // Remove after https://issuetracker.google.com/135275901
- void focus(
- @NonNull Rect focus,
- @NonNull Rect metering,
- @NonNull Executor executor,
- @NonNull OnFocusListener listener);
-
- /**
- * Adjusts the camera output according to the properties in some local regions.
- *
- * <p>The auto-focus (AF), auto-exposure (AE) and auto-whitebalance (AWB) properties will be
- * recalculated from the local regions.
- *
- * @param focus rectangle with dimensions in sensor coordinate frame for focus
- * @param metering rectangle with dimensions in sensor coordinate frame for metering
- */
- void focus(@NonNull Rect focus, @NonNull Rect metering);
-
/** Returns the current flash mode. */
@NonNull
FlashMode getFlashMode();
@@ -100,9 +67,6 @@
/** Returns if current torch is enabled or not. */
boolean isTorchOn();
- /** Returns if the focus is currently locked or not. */
- boolean isFocusLocked();
-
/** Performs a AF trigger. */
void triggerAf();
@@ -122,15 +86,6 @@
public void setCropRegion(@Nullable Rect crop) {
}
- @Override
- public void focus(@NonNull Rect focus, @NonNull Rect metering, @Nullable Executor executor,
- @Nullable OnFocusListener listener) {
- }
-
- @Override
- public void focus(@NonNull Rect focus, @NonNull Rect metering) {
- }
-
@NonNull
@Override
public FlashMode getFlashMode() {
@@ -151,11 +106,6 @@
}
@Override
- public boolean isFocusLocked() {
- return false;
- }
-
- @Override
public void triggerAf() {
}
@@ -171,6 +121,16 @@
@Override
public void submitCaptureRequests(@NonNull List<CaptureConfig> captureConfigs) {
}
+
+ @Override
+ public void startFocusAndMetering(@NonNull FocusMeteringAction action) {
+
+ }
+
+ @Override
+ public void cancelFocusAndMetering() {
+
+ }
};
/** Listener called when CameraControlInternal need to notify event. */
diff --git a/camera/camera-core/src/main/java/androidx/camera/core/CameraDeviceStateCallbacks.java b/camera/camera-core/src/main/java/androidx/camera/core/CameraDeviceStateCallbacks.java
index d27affa..89a1302 100644
--- a/camera/camera-core/src/main/java/androidx/camera/core/CameraDeviceStateCallbacks.java
+++ b/camera/camera-core/src/main/java/androidx/camera/core/CameraDeviceStateCallbacks.java
@@ -18,6 +18,7 @@
import android.hardware.camera2.CameraDevice;
+import androidx.annotation.NonNull;
import androidx.annotation.RestrictTo;
import androidx.annotation.RestrictTo.Scope;
@@ -65,19 +66,19 @@
static final class NoOpDeviceStateCallback extends CameraDevice.StateCallback {
@Override
- public void onOpened(CameraDevice cameraDevice) {
+ public void onOpened(@NonNull CameraDevice cameraDevice) {
}
@Override
- public void onClosed(CameraDevice cameraDevice) {
+ public void onClosed(@NonNull CameraDevice cameraDevice) {
}
@Override
- public void onDisconnected(CameraDevice cameraDevice) {
+ public void onDisconnected(@NonNull CameraDevice cameraDevice) {
}
@Override
- public void onError(CameraDevice cameraDevice, int error) {
+ public void onError(@NonNull CameraDevice cameraDevice, int error) {
}
}
@@ -94,28 +95,28 @@
}
@Override
- public void onOpened(CameraDevice cameraDevice) {
+ public void onOpened(@NonNull CameraDevice cameraDevice) {
for (CameraDevice.StateCallback callback : mCallbacks) {
callback.onOpened(cameraDevice);
}
}
@Override
- public void onClosed(CameraDevice cameraDevice) {
+ public void onClosed(@NonNull CameraDevice cameraDevice) {
for (CameraDevice.StateCallback callback : mCallbacks) {
callback.onClosed(cameraDevice);
}
}
@Override
- public void onDisconnected(CameraDevice cameraDevice) {
+ public void onDisconnected(@NonNull CameraDevice cameraDevice) {
for (CameraDevice.StateCallback callback : mCallbacks) {
callback.onDisconnected(cameraDevice);
}
}
@Override
- public void onError(CameraDevice cameraDevice, int error) {
+ public void onError(@NonNull CameraDevice cameraDevice, int error) {
for (CameraDevice.StateCallback callback : mCallbacks) {
callback.onError(cameraDevice, error);
}
diff --git a/camera/camera-core/src/main/java/androidx/camera/core/CameraRepository.java b/camera/camera-core/src/main/java/androidx/camera/core/CameraRepository.java
index 54e7fad..1e7d24a 100644
--- a/camera/camera-core/src/main/java/androidx/camera/core/CameraRepository.java
+++ b/camera/camera-core/src/main/java/androidx/camera/core/CameraRepository.java
@@ -20,11 +20,18 @@
import android.util.Log;
import androidx.annotation.GuardedBy;
+import androidx.annotation.NonNull;
import androidx.annotation.RestrictTo;
import androidx.annotation.RestrictTo.Scope;
+import androidx.camera.core.impl.utils.executor.CameraXExecutors;
+import androidx.camera.core.impl.utils.futures.Futures;
+import androidx.concurrent.futures.CallbackToFutureAdapter;
+import androidx.core.util.Preconditions;
-import java.util.Collections;
+import com.google.common.util.concurrent.ListenableFuture;
+
import java.util.HashMap;
+import java.util.HashSet;
import java.util.Map;
import java.util.Set;
@@ -41,6 +48,12 @@
@GuardedBy("mCamerasLock")
private final Map<String, BaseCamera> mCameras = new HashMap<>();
+ @GuardedBy("mCamerasLock")
+ private final Set<BaseCamera> mReleasingCameras = new HashSet<>();
+ @GuardedBy("mCamerasLock")
+ private ListenableFuture<Void> mDeinitFuture;
+ @GuardedBy("mCamerasLock")
+ private CallbackToFutureAdapter.Completer<Void> mDeinitCompleter;
/**
* Initializes the repository from a {@link Context}.
@@ -65,12 +78,73 @@
}
/**
+ * Clear and release all cameras from the repository.
+ *
+ * @hide
+ */
+ @RestrictTo(Scope.LIBRARY_GROUP)
+ @NonNull
+ public ListenableFuture<Void> deinit() {
+ synchronized (mCamerasLock) {
+ // If the camera list is empty, we can either return the current deinit future that
+ // has not yet completed, or an immediate successful future if we are already
+ // completely deinitialized.
+ if (mCameras.isEmpty()) {
+ return mDeinitFuture == null ? Futures.immediateFuture(null) : mDeinitFuture;
+ }
+
+ ListenableFuture<Void> currentFuture = mDeinitFuture;
+ if (currentFuture == null) {
+ // Create a single future that will be used to track closing of all cameras.
+ // Once all cameras have been released, this future will complete. This future
+ // will stay active until all cameras in mReleasingCameras has completed, even if
+ // CameraRepository is initialized and deinitialized multiple times in quick
+ // succession.
+ currentFuture = CallbackToFutureAdapter.getFuture((completer) -> {
+ Preconditions.checkState(Thread.holdsLock(mCamerasLock));
+ mDeinitCompleter = completer;
+ return "CameraRepository-deinit";
+ });
+ mDeinitFuture = currentFuture;
+ }
+
+ for (final BaseCamera camera : mCameras.values()) {
+ // Release the camera and wait for it to complete. We keep track of which cameras
+ // are still releasing with mReleasingCameras.
+ mReleasingCameras.add(camera);
+ camera.release().addListener(() -> {
+ synchronized (mCamerasLock) {
+ // When the camera has completed releasing, we can now remove it from
+ // mReleasingCameras. Any time a camera finishes releasing, we need to
+ // check if all cameras a finished so we can finish the future which is
+ // waiting for all cameras to release.
+ mReleasingCameras.remove(camera);
+ if (mReleasingCameras.isEmpty()) {
+ Preconditions.checkNotNull(mDeinitCompleter);
+ // Every camera has been released. Signal successful completion of
+ // deinit().
+ mDeinitCompleter.set(null);
+ mDeinitCompleter = null;
+ mDeinitFuture = null;
+ }
+ }
+ }, CameraXExecutors.directExecutor());
+ }
+
+ // Ensure all cameras are removed from the active "mCameras" map. This map can be
+ // repopulated by #init().
+ mCameras.clear();
+
+ return currentFuture;
+ }
+ }
+
+ /**
* Gets a {@link BaseCamera} for the given id.
*
* @param cameraId id for the camera
* @return a {@link BaseCamera} paired to this id
* @throws IllegalArgumentException if there is no camera paired with the id
- *
* @hide
*/
@RestrictTo(Scope.LIBRARY_GROUP)
@@ -93,7 +167,7 @@
*/
Set<String> getCameraIds() {
synchronized (mCamerasLock) {
- return Collections.unmodifiableSet(mCameras.keySet());
+ return new HashSet<>(mCameras.keySet());
}
}
diff --git a/camera/camera-core/src/main/java/androidx/camera/core/CameraX.java b/camera/camera-core/src/main/java/androidx/camera/core/CameraX.java
index fa8fec7..8393fef 100644
--- a/camera/camera-core/src/main/java/androidx/camera/core/CameraX.java
+++ b/camera/camera-core/src/main/java/androidx/camera/core/CameraX.java
@@ -405,9 +405,7 @@
* @return the {@link CameraControl}.
* @throws CameraInfoUnavailableException if unable to access cameras, perhaps due to
* insufficient permissions.
- * @hide
*/
- @RestrictTo(Scope.LIBRARY_GROUP)
public static CameraControl getCameraControl(LensFacing lensFacing)
throws CameraInfoUnavailableException {
@@ -488,6 +486,19 @@
}
/**
+ * Deinitializes CameraX so that it can be initialized again.
+ *
+ * <p>Note: This is only for testing purpose for now.
+ * TODO(b/138544571): Release all cameras in CameraX.deinit
+ *
+ * @hide
+ */
+ @RestrictTo(Scope.TESTS)
+ public static void deinit() {
+ INSTANCE.deinitInternal();
+ }
+
+ /**
* Returns the context used for CameraX.
*
* @hide
@@ -695,6 +706,11 @@
mCameraRepository.init(mCameraFactory);
}
+ private void deinitInternal() {
+ mInitialized.set(false);
+ mCameraRepository.deinit();
+ }
+
private UseCaseGroupLifecycleController getOrCreateUseCaseGroup(LifecycleOwner lifecycleOwner) {
return mUseCaseGroupRepository.getOrCreateUseCaseGroup(
lifecycleOwner, new UseCaseGroupRepository.UseCaseGroupSetup() {
diff --git a/camera/camera-core/src/main/java/androidx/camera/core/DisplayOrientedMeteringPointFactory.java b/camera/camera-core/src/main/java/androidx/camera/core/DisplayOrientedMeteringPointFactory.java
new file mode 100644
index 0000000..212a14d
--- /dev/null
+++ b/camera/camera-core/src/main/java/androidx/camera/core/DisplayOrientedMeteringPointFactory.java
@@ -0,0 +1,164 @@
+/*
+ * 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.camera.core;
+
+import android.content.Context;
+import android.graphics.PointF;
+import android.view.Display;
+import android.view.WindowManager;
+
+import androidx.annotation.NonNull;
+import androidx.camera.core.CameraX.LensFacing;
+
+/**
+ * A {@link MeteringPointFactory} that can create {@link MeteringPoint} by display oriented x, y.
+ *
+ * <p>This factory will consider the current display rotation and the lens facing to translate the
+ * x/y correctly. Using this factory, apps do not need to handle the device rotation. They
+ * can simply pass the x/y retrieved from their View. However if the camera preview is cropped,
+ * scaled or rotated, it is apps' duty to transform the coordinates first.
+ *
+ * <p> The width/height of this factory is the logical width/height of the preview FoV and X/Y
+ * is the logical XY inside the FOV. User can set the width and height to 1.0 which will make the
+ * XY the normalized coordinates [0..1].
+ */
+public final class DisplayOrientedMeteringPointFactory extends MeteringPointFactory {
+ /** The logical width of FoV in current display orientation */
+ private final float mWidth;
+ /** The logical height of FoV in current display orientation */
+ private final float mHeight;
+ /** Lens facing is required for correctly adjusted for front camera */
+ private final LensFacing mLensFacing;
+ /** {@link Display} used for detecting display orientation */
+ @NonNull
+ private final Display mDisplay;
+ @NonNull
+ private final CameraInfo mCameraInfo;
+
+ /**
+ * Creates the {@link MeteringPointFactory} with default display orientation.
+ *
+ * <p>The width/height is the logical width/height of the preview FoV and X/Y is the logical
+ * XY inside the FOV. User can set the width and height to 1.0 which will make the XY the
+ * normalized coordinates [0..1]. Or user can set the width/height to the View width/height and
+ * then X/Y becomes the X/Y in the view.
+ *
+ * @param context context to get the {@link WindowManager} for default display rotation.
+ * @param lensFacing current lens facing.
+ * @param width the logical width of FoV in current display orientation.
+ * @param height the logical height of FoV in current display orientation.
+ */
+ public DisplayOrientedMeteringPointFactory(@NonNull Context context,
+ @NonNull LensFacing lensFacing, float width, float height) {
+ this(((WindowManager) context.getSystemService(Context.WINDOW_SERVICE)).getDefaultDisplay(),
+ lensFacing, width, height);
+ }
+
+ /**
+ * Creates the {@link MeteringPointFactory} with custom display orientation. This is used
+ * in multi-display situation.
+ *
+ * <p>The width/height is the logical width/height of the preview FoV and X/Y is the logical
+ * XY inside the FOV. User can set the width and height to 1.0 which will make the XY the
+ * normalized coordinates [0..1]. Or user can set the width/height to the View width/height and
+ * then X/Y becomes the X/Y in the view.
+ * {@link Display} is used to dete
+ * @param display {@link Display} to get the orientation from.
+ * @param lensFacing current lens facing.
+ * @param width the logical width of FoV in current display orientation.
+ * @param height the logical height of FoV in current display orientation.
+ */
+ public DisplayOrientedMeteringPointFactory(@NonNull Display display,
+ @NonNull LensFacing lensFacing, float width, float height) {
+ mWidth = width;
+ mHeight = height;
+ mLensFacing = lensFacing;
+ mDisplay = display;
+ try {
+ String cameraId = CameraX.getCameraWithLensFacing(lensFacing);
+ mCameraInfo = CameraX.getCameraInfo(cameraId);
+ } catch (Exception e) {
+ throw new IllegalArgumentException("Can not find CameraInfo : " + lensFacing);
+ }
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @NonNull
+ @Override
+ protected PointF translatePoint(float x, float y) {
+ float width = mWidth;
+ float height = mHeight;
+
+ boolean compensateForMirroring = (mLensFacing == LensFacing.FRONT);
+ int relativeCameraOrientation = getRelativeCameraOrientation(compensateForMirroring);
+ float outputX = x;
+ float outputY = y;
+ float outputWidth = width;
+ float outputHeight = height;
+
+ if (relativeCameraOrientation == 90 || relativeCameraOrientation == 270) {
+ // We're horizontal. Swap width/height. Swap x/y.
+ outputX = y;
+ outputY = x;
+ outputWidth = height;
+ outputHeight = width;
+ }
+
+ switch (relativeCameraOrientation) {
+ // Map to correct coordinates according to relativeCameraOrientation
+ case 90:
+ outputY = outputHeight - outputY;
+ break;
+ case 180:
+ outputX = outputWidth - outputX;
+ outputY = outputHeight - outputY;
+ break;
+ case 270:
+ outputX = outputWidth - outputX;
+ break;
+ default:
+ break;
+ }
+
+ // Swap x if it's a mirrored preview
+ if (compensateForMirroring) {
+ outputX = outputWidth - outputX;
+ }
+
+ // Normalized it to [0, 1]
+ outputX = outputX / outputWidth;
+ outputY = outputY / outputHeight;
+
+ return new PointF(outputX, outputY);
+ }
+
+ private int getRelativeCameraOrientation(boolean compensateForMirroring) {
+ int rotationDegrees;
+ try {
+ int displayRotation = mDisplay.getRotation();
+ rotationDegrees = mCameraInfo.getSensorRotationDegrees(displayRotation);
+ if (compensateForMirroring) {
+ rotationDegrees = (360 - rotationDegrees) % 360;
+ }
+ } catch (Exception e) {
+ rotationDegrees = 0;
+ }
+ return rotationDegrees;
+ }
+}
diff --git a/camera/camera-core/src/main/java/androidx/camera/core/FocusMeteringAction.java b/camera/camera-core/src/main/java/androidx/camera/core/FocusMeteringAction.java
new file mode 100644
index 0000000..db1b5b2
--- /dev/null
+++ b/camera/camera-core/src/main/java/androidx/camera/core/FocusMeteringAction.java
@@ -0,0 +1,310 @@
+/*
+ * 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.camera.core;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.annotation.RestrictTo;
+import androidx.annotation.VisibleForTesting;
+import androidx.camera.core.impl.utils.executor.CameraXExecutors;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.Executor;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+/**
+ * A configuration used to trigger a focus and/or metering action.
+ *
+ * <p>To construct a {@link FocusMeteringAction}, apps have to create a {@link Builder} by
+ * {@link Builder#from(MeteringPoint)} or {@link Builder#from(MeteringPoint, MeteringMode)}.
+ * {@link MeteringPoint} is a point used to specify the focus/metering areas. Apps can use various
+ * {@link MeteringPointFactory} to create the points. When the {@link FocusMeteringAction} is built,
+ * pass it to {@link CameraControl#startFocusAndMetering(FocusMeteringAction)} to initiate the focus
+ * and metering action.
+ *
+ * <p>The default {@link MeteringMode} is {@link MeteringMode#AF_AE_AWB} which means the point is
+ * used for all AF/AE/AWB regions. Apps can set the proper {@link MeteringMode} to optionally
+ * exclude some 3A regions. Multiple regions for specific 3A type are also supported via
+ * {@link Builder#addPoint(MeteringPoint)} or
+ * {@link Builder#addPoint(MeteringPoint, MeteringMode)}. App can also this API to enable
+ * different region for AF and AE respectively.
+ *
+ * <p>If any AF points are specified, it will trigger AF to start a manual AF scan and cancel AF
+ * trigger when {@link CameraControl#cancelFocusAndMetering()} is called. When triggering AF is
+ * done, it will call the {@link OnAutoFocusListener#onFocusCompleted(boolean)} which is set via
+ * {@link Builder#setAutoFocusCallback(OnAutoFocusListener)}. If AF point is not specified or
+ * the action is cancelled before AF is locked, CameraX will call the
+ * {@link OnAutoFocusListener#onFocusCompleted(boolean)} with isFocusLocked set to false.
+ *
+ * <p>App can set a auto-cancel duration to let CameraX call
+ * {@link CameraControl#cancelFocusAndMetering()} automatically in the specified duration. By
+ * default the auto-cancel duration is 5 seconds. Apps can call {@link Builder#disableAutoCancel()}
+ * to disable auto-cancel.
+ */
+public class FocusMeteringAction {
+ static final MeteringMode DEFAULT_METERINGMODE = MeteringMode.AF_AE_AWB;
+ static final long DEFAULT_AUTOCANCEL_DURATION = 5000;
+ private final List<MeteringPoint> mMeteringPointsAF;
+ private final List<MeteringPoint> mMeteringPointsAE;
+ private final List<MeteringPoint> mMeteringPointsAWB;
+ private final Executor mListenerExecutor;
+ @SuppressWarnings("WeakerAccess") /* synthetic accessor */
+ final OnAutoFocusListener mOnAutoFocusListener;
+ private final long mAutoCancelDurationInMs;
+ private AtomicBoolean mHasNotifiedListener = new AtomicBoolean(false);
+
+ @SuppressWarnings("WeakerAccess") /* synthetic accessor */
+ FocusMeteringAction(Builder builder) {
+ mMeteringPointsAF = builder.mMeteringPointsAF;
+ mMeteringPointsAE = builder.mMeteringPointsAE;
+ mMeteringPointsAWB = builder.mMeteringPointsAWB;
+ mListenerExecutor = builder.mListenerExecutor;
+ mOnAutoFocusListener = builder.mOnAutoFocusListener;
+ mAutoCancelDurationInMs = builder.mAutoCancelDurationInMs;
+ }
+
+ /**
+ * Returns current {@link OnAutoFocusListener}.
+ */
+ @Nullable
+ public OnAutoFocusListener getOnAutoFocusListener() {
+ return mOnAutoFocusListener;
+ }
+
+ /**
+ * Returns auto-cancel duration. Returns 0 if auto-cancel is disabled.
+ */
+ public long getAutoCancelDurationInMs() {
+ return mAutoCancelDurationInMs;
+ }
+
+ /**
+ * Returns all {@link MeteringPoint}s used for AF regions.
+ */
+ @NonNull
+ public List<MeteringPoint> getMeteringPointsAF() {
+ return mMeteringPointsAF;
+ }
+
+ /**
+ * Returns all {@link MeteringPoint}s used for AE regions.
+ */
+ @NonNull
+ public List<MeteringPoint> getMeteringPointsAE() {
+ return mMeteringPointsAE;
+ }
+
+ /**
+ * Returns all {@link MeteringPoint}s used for AWB regions.
+ */
+ @NonNull
+ public List<MeteringPoint> getMeteringPointsAWB() {
+ return mMeteringPointsAWB;
+ }
+
+ /**
+ * Returns if auto-cancel is enabled or not.
+ */
+ public boolean isAutoCancelEnabled() {
+ return mAutoCancelDurationInMs != 0;
+ }
+
+ @VisibleForTesting
+ Executor getListenerExecutor() {
+ return mListenerExecutor;
+ }
+
+ /**
+ * Notifies current {@link OnAutoFocusListener} and ensures it is called once.
+ *
+ * @hide
+ */
+ @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
+ public void notifyAutoFocusCompleted(boolean isFocused) {
+ if (!mHasNotifiedListener.getAndSet(true)) {
+ if (mOnAutoFocusListener != null) {
+ mListenerExecutor.execute(new Runnable() {
+ @Override
+ public void run() {
+ mOnAutoFocusListener.onFocusCompleted(isFocused);
+ }
+ });
+ }
+ }
+ }
+
+ /**
+ * Listener for receiving auto-focus completion event.
+ */
+ public interface OnAutoFocusListener {
+ /**
+ * Called when camera auto focus completes or when the action is cancelled before
+ * auto-focus completes.
+ *
+ * @param isFocusLocked true if focus is locked successfully, false otherwise.
+ */
+ void onFocusCompleted(boolean isFocusLocked);
+ }
+
+ /**
+ * Focus/Metering mode used to specify which 3A regions is activated for corresponding
+ * {@link MeteringPoint}.
+ */
+ public enum MeteringMode {
+ AF_AE_AWB,
+ AF_AE,
+ AE_AWB,
+ AF_AWB,
+ AF_ONLY,
+ AE_ONLY,
+ AWB_ONLY
+ }
+
+ /**
+ * The builder used to create the {@link FocusMeteringAction}. App must use
+ * {@link Builder#from(MeteringPoint)}
+ * or {@link Builder#from(MeteringPoint, MeteringMode)} to create the {@link Builder}.
+ */
+ public static class Builder {
+ @SuppressWarnings("WeakerAccess") /* synthetic accessor */
+ final List<MeteringPoint> mMeteringPointsAF = new ArrayList<>();
+ @SuppressWarnings("WeakerAccess") /* synthetic accessor */
+ final List<MeteringPoint> mMeteringPointsAE = new ArrayList<>();
+ @SuppressWarnings("WeakerAccess") /* synthetic accessor */
+ final List<MeteringPoint> mMeteringPointsAWB = new ArrayList<>();
+ @SuppressWarnings("WeakerAccess") /* synthetic accessor */
+ OnAutoFocusListener mOnAutoFocusListener = null;
+ @SuppressWarnings("WeakerAccess") /* synthetic accessor */
+ Executor mListenerExecutor = CameraXExecutors.mainThreadExecutor();
+ @SuppressWarnings("WeakerAccess") /* synthetic accessor */
+ long mAutoCancelDurationInMs = DEFAULT_AUTOCANCEL_DURATION;
+
+ private Builder(@NonNull MeteringPoint point) {
+ this(point, DEFAULT_METERINGMODE);
+ }
+
+ private Builder(@NonNull MeteringPoint point, @NonNull MeteringMode mode) {
+ addPoint(point, mode);
+ }
+
+ /**
+ * Creates the Builder from a {@link MeteringPoint} with default {@link MeteringMode}.
+ */
+ @NonNull
+ public static Builder from(@NonNull MeteringPoint meteringPoint) {
+ return new Builder(meteringPoint);
+ }
+
+ /**
+ * Creates the Builder from a {@link MeteringPoint} and {@link MeteringMode}
+ */
+ @NonNull
+ public static Builder from(@NonNull MeteringPoint meteringPoint,
+ @NonNull MeteringMode mode) {
+ return new Builder(meteringPoint, mode);
+ }
+
+ /**
+ * Adds another {@link MeteringPoint} with default {@link MeteringMode}.
+ */
+ @NonNull
+ public Builder addPoint(@NonNull MeteringPoint point) {
+ return addPoint(point, DEFAULT_METERINGMODE);
+ }
+
+ /**
+ * Adds another {@link MeteringPoint} with specified {@link MeteringMode}.
+ */
+ @NonNull
+ public Builder addPoint(@NonNull MeteringPoint point, @NonNull MeteringMode mode) {
+ if (mode == MeteringMode.AF_AE_AWB
+ || mode == MeteringMode.AF_AE
+ || mode == MeteringMode.AF_AWB
+ || mode == MeteringMode.AF_ONLY) {
+ mMeteringPointsAF.add(point);
+ }
+
+ if (mode == MeteringMode.AF_AE_AWB
+ || mode == MeteringMode.AF_AE
+ || mode == MeteringMode.AE_AWB
+ || mode == MeteringMode.AE_ONLY) {
+ mMeteringPointsAE.add(point);
+ }
+
+ if (mode == MeteringMode.AF_AE_AWB
+ || mode == MeteringMode.AE_AWB
+ || mode == MeteringMode.AF_AWB
+ || mode == MeteringMode.AWB_ONLY) {
+ mMeteringPointsAWB.add(point);
+ }
+ return this;
+ }
+
+ /**
+ * Sets the {@link OnAutoFocusListener} to be notified when auto-focus completes. The
+ * listener is called on main thread.
+ */
+ @NonNull
+ public Builder setAutoFocusCallback(@NonNull OnAutoFocusListener listener) {
+ mOnAutoFocusListener = listener;
+ return this;
+ }
+
+ /**
+ * Sets the {@link OnAutoFocusListener} to be notified when auto-focus completes. The
+ * listener is called on specified {@link Executor}.
+ */
+ @NonNull
+ public Builder setAutoFocusCallback(@NonNull Executor executor,
+ @NonNull OnAutoFocusListener listener) {
+ mListenerExecutor = executor;
+ mOnAutoFocusListener = listener;
+ return this;
+ }
+
+ /**
+ * Sets the auto-cancel duration. After set, {@link CameraControl#cancelFocusAndMetering()}
+ * will be called in specified duration. By default, auto-cancel is enabled with 5
+ * seconds duration.
+ */
+ @NonNull
+ public Builder setAutoCancelDuration(long duration, @NonNull TimeUnit timeUnit) {
+ mAutoCancelDurationInMs = timeUnit.toMillis(duration);
+ return this;
+ }
+
+ /**
+ * Disables the auto-cancel.
+ */
+ @NonNull
+ public Builder disableAutoCancel() {
+ mAutoCancelDurationInMs = 0;
+ return this;
+ }
+
+ /**
+ * Builds the {@link FocusMeteringAction} instance.
+ */
+ @NonNull
+ public FocusMeteringAction build() {
+ return new FocusMeteringAction(this);
+ }
+
+ }
+}
diff --git a/camera/camera-core/src/main/java/androidx/camera/core/ImageAnalysis.java b/camera/camera-core/src/main/java/androidx/camera/core/ImageAnalysis.java
index d8d48ac..506d080 100644
--- a/camera/camera-core/src/main/java/androidx/camera/core/ImageAnalysis.java
+++ b/camera/camera-core/src/main/java/androidx/camera/core/ImageAnalysis.java
@@ -63,9 +63,10 @@
final AtomicInteger mRelativeRotation = new AtomicInteger();
final Handler mHandler;
private final ImageAnalysisConfig.Builder mUseCaseConfigBuilder;
- private final ImageAnalysisBlockingCallback mImageAnalysisBlockingCallback;
@SuppressWarnings("WeakerAccess") /* synthetic access */
- final ImageAnalysisNonBlockingCallback mImageAnalysisNonBlockingCallback;
+ final ImageAnalysisBlockingAnalyzer mImageAnalysisBlockingAnalyzer;
+ @SuppressWarnings("WeakerAccess") /* synthetic access */
+ final ImageAnalysisNonBlockingAnalyzer mImageAnalysisNonBlockingAnalyzer;
@Nullable
ImageReaderProxy mImageReader;
@Nullable
@@ -89,10 +90,10 @@
}
setImageFormat(ImageReaderFormatRecommender.chooseCombo().imageAnalysisFormat());
// Init both instead of lazy loading to void synchronization.
- mImageAnalysisBlockingCallback = new ImageAnalysisBlockingCallback(mSubscribedAnalyzer,
+ mImageAnalysisBlockingAnalyzer = new ImageAnalysisBlockingAnalyzer(mSubscribedAnalyzer,
mRelativeRotation,
mHandler);
- mImageAnalysisNonBlockingCallback = new ImageAnalysisNonBlockingCallback(
+ mImageAnalysisNonBlockingAnalyzer = new ImageAnalysisNonBlockingAnalyzer(
mSubscribedAnalyzer, mRelativeRotation,
mHandler, config.getBackgroundExecutor(
CameraXExecutors.highPriorityExecutor()));
@@ -220,7 +221,8 @@
new DeferrableSurface.OnSurfaceDetachedListener() {
@Override
public void onSurfaceDetached() {
- mImageAnalysisNonBlockingCallback.close();
+ mImageAnalysisNonBlockingAnalyzer.close();
+ mImageAnalysisBlockingAnalyzer.close();
if (mImageReader != null) {
mImageReader.close();
mImageReader = null;
@@ -292,10 +294,11 @@
ImageReaderProxy.OnImageAvailableListener onImageAvailableListener;
if (config.getImageReaderMode() == ImageReaderMode.ACQUIRE_NEXT_IMAGE) {
- onImageAvailableListener = mImageAnalysisBlockingCallback;
+ onImageAvailableListener = mImageAnalysisBlockingAnalyzer;
+ mImageAnalysisBlockingAnalyzer.open();
} else {
- onImageAvailableListener = mImageAnalysisNonBlockingCallback;
- mImageAnalysisNonBlockingCallback.open();
+ onImageAvailableListener = mImageAnalysisNonBlockingAnalyzer;
+ mImageAnalysisNonBlockingAnalyzer.open();
}
mImageReader.setOnImageAvailableListener(onImageAvailableListener, backgroundExecutor);
diff --git a/camera/camera-core/src/main/java/androidx/camera/core/ImageAnalysisAbstractAnalyzer.java b/camera/camera-core/src/main/java/androidx/camera/core/ImageAnalysisAbstractAnalyzer.java
new file mode 100644
index 0000000..74b18c4
--- /dev/null
+++ b/camera/camera-core/src/main/java/androidx/camera/core/ImageAnalysisAbstractAnalyzer.java
@@ -0,0 +1,79 @@
+/*
+ * 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.camera.core;
+
+import android.os.Handler;
+
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.concurrent.atomic.AtomicReference;
+
+/**
+ * Abstract Analyzer that wraps around {@link ImageAnalysis.Analyzer} and implements
+ * {@link ImageReaderProxy.OnImageAvailableListener}.
+ *
+ * This is an extension of {@link ImageAnalysis}. It has the same lifecycle and share part of the
+ * states.
+ */
+abstract class ImageAnalysisAbstractAnalyzer implements ImageReaderProxy.OnImageAvailableListener {
+
+ // Member variables from ImageAnalysis.
+ private final AtomicReference<ImageAnalysis.Analyzer> mSubscribedAnalyzer;
+ private final AtomicInteger mRelativeRotation;
+ final Handler mUserHandler;
+
+ // Flag that reflects the state of ImageAnalysis.
+ private AtomicBoolean mIsClosed;
+
+ ImageAnalysisAbstractAnalyzer(AtomicReference<ImageAnalysis.Analyzer> subscribedAnalyzer,
+ AtomicInteger relativeRotation, Handler userHandler) {
+ mSubscribedAnalyzer = subscribedAnalyzer;
+ mRelativeRotation = relativeRotation;
+ mUserHandler = userHandler;
+ mIsClosed = new AtomicBoolean(false);
+ }
+
+ /**
+ * Analyzes a {@link ImageProxy} using the wrapped {@link ImageAnalysis.Analyzer}.
+ */
+ void analyzeImage(ImageProxy imageProxy) {
+ ImageAnalysis.Analyzer analyzer = mSubscribedAnalyzer.get();
+ if (analyzer != null && !isClosed()) {
+ // When the analyzer exists and ImageAnalysis is active.
+ analyzer.analyze(imageProxy, mRelativeRotation.get());
+ }
+ }
+
+ /**
+ * Initialize the callback.
+ */
+ void open() {
+ mIsClosed.set(false);
+ }
+
+ /**
+ * Closes the callback so that it will stop posting to analyzer.
+ */
+ void close() {
+ mIsClosed.set(true);
+ }
+
+ boolean isClosed() {
+ return mIsClosed.get();
+ }
+
+}
diff --git a/camera/camera-core/src/main/java/androidx/camera/core/ImageAnalysisBlockingCallback.java b/camera/camera-core/src/main/java/androidx/camera/core/ImageAnalysisBlockingAnalyzer.java
similarity index 66%
rename from camera/camera-core/src/main/java/androidx/camera/core/ImageAnalysisBlockingCallback.java
rename to camera/camera-core/src/main/java/androidx/camera/core/ImageAnalysisBlockingAnalyzer.java
index ce59f70..1283f8d 100644
--- a/camera/camera-core/src/main/java/androidx/camera/core/ImageAnalysisBlockingCallback.java
+++ b/camera/camera-core/src/main/java/androidx/camera/core/ImageAnalysisBlockingAnalyzer.java
@@ -26,20 +26,12 @@
*
* <p> Used with {@link ImageAnalysis}.
*/
-final class ImageAnalysisBlockingCallback implements ImageReaderProxy.OnImageAvailableListener {
+final class ImageAnalysisBlockingAnalyzer extends ImageAnalysisAbstractAnalyzer {
- private static final String TAG = "BlockingCallback";
-
- final AtomicReference<ImageAnalysis.Analyzer> mSubscribedAnalyzer;
- final AtomicInteger mRelativeRotation;
-
- private final Handler mUserHandler;
-
- ImageAnalysisBlockingCallback(AtomicReference<ImageAnalysis.Analyzer> subscribedAnalyzer,
+ ImageAnalysisBlockingAnalyzer(
+ AtomicReference<ImageAnalysis.Analyzer> subscribedAnalyzer,
AtomicInteger relativeRotation, Handler userHandler) {
- mSubscribedAnalyzer = subscribedAnalyzer;
- mRelativeRotation = relativeRotation;
- mUserHandler = userHandler;
+ super(subscribedAnalyzer, relativeRotation, userHandler);
}
@Override
@@ -53,10 +45,7 @@
@Override
public void run() {
try {
- ImageAnalysis.Analyzer analyzer = mSubscribedAnalyzer.get();
- if (analyzer != null) {
- analyzer.analyze(image, mRelativeRotation.get());
- }
+ analyzeImage(image);
} finally {
image.close();
}
diff --git a/camera/camera-core/src/main/java/androidx/camera/core/ImageAnalysisNonBlockingCallback.java b/camera/camera-core/src/main/java/androidx/camera/core/ImageAnalysisNonBlockingAnalyzer.java
similarity index 80%
rename from camera/camera-core/src/main/java/androidx/camera/core/ImageAnalysisNonBlockingCallback.java
rename to camera/camera-core/src/main/java/androidx/camera/core/ImageAnalysisNonBlockingAnalyzer.java
index 447255a..fe8420e 100644
--- a/camera/camera-core/src/main/java/androidx/camera/core/ImageAnalysisNonBlockingCallback.java
+++ b/camera/camera-core/src/main/java/androidx/camera/core/ImageAnalysisNonBlockingAnalyzer.java
@@ -23,7 +23,6 @@
import androidx.annotation.NonNull;
import java.util.concurrent.Executor;
-import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicLong;
import java.util.concurrent.atomic.AtomicReference;
@@ -34,39 +33,30 @@
*
* <p> Used with {@link ImageAnalysis}.
*/
-final class ImageAnalysisNonBlockingCallback implements ImageReaderProxy.OnImageAvailableListener {
+final class ImageAnalysisNonBlockingAnalyzer extends ImageAnalysisAbstractAnalyzer {
private static final String TAG = "NonBlockingCallback";
- @SuppressWarnings("WeakerAccess") /* synthetic access */
- final AtomicReference<ImageAnalysis.Analyzer> mSubscribedAnalyzer;
- @SuppressWarnings("WeakerAccess") /* synthetic access */
- final AtomicInteger mRelativeRotation;
+ // The executor for managing cached image.
@SuppressWarnings("WeakerAccess") /* synthetic access */
final Executor mBackgroundExecutor;
- private final Handler mUserHandler;
// The cached image when analyzer is busy. Image removed from cache must be closed by 1) closing
// it directly or 2) re-posting it to close it eventually.
@GuardedBy("this")
private ImageProxy mCachedImage;
- private AtomicBoolean mIsClosed;
-
// Timestamp of the last image posted to user callback thread.
private final AtomicLong mPostedImageTimestamp;
// Timestamp of the last image finished being processed by user callback thread.
private final AtomicLong mFinishedImageTimestamp;
- ImageAnalysisNonBlockingCallback(AtomicReference<ImageAnalysis.Analyzer> subscribedAnalyzer,
+ ImageAnalysisNonBlockingAnalyzer(AtomicReference<ImageAnalysis.Analyzer> subscribedAnalyzer,
AtomicInteger relativeRotation, Handler userHandler, Executor executor) {
- mSubscribedAnalyzer = subscribedAnalyzer;
- mRelativeRotation = relativeRotation;
- mUserHandler = userHandler;
+ super(subscribedAnalyzer, relativeRotation, userHandler);
mBackgroundExecutor = executor;
mPostedImageTimestamp = new AtomicLong();
mFinishedImageTimestamp = new AtomicLong();
- mIsClosed = new AtomicBoolean();
open();
}
@@ -79,21 +69,18 @@
analyze(imageProxy);
}
- /**
- * Initialize the callback.
- */
+ @Override
synchronized void open() {
+ super.open();
mCachedImage = null;
mPostedImageTimestamp.set(-1);
mFinishedImageTimestamp.set(mPostedImageTimestamp.get());
- mIsClosed.set(false);
}
- /**
- * Closes the callback so that it will stop posting to analyzer.
- */
+
+ @Override
synchronized void close() {
- mIsClosed.set(true);
+ super.close();
if (mCachedImage != null) {
mCachedImage.close();
mCachedImage = null;
@@ -118,7 +105,7 @@
* @param imageProxy the incoming image frame.
*/
private synchronized void analyze(@NonNull ImageProxy imageProxy) {
- if (mIsClosed.get()) {
+ if (isClosed()) {
return;
}
long postedImageTimestamp = mPostedImageTimestamp.get();
@@ -145,10 +132,7 @@
@Override
public void run() {
try {
- ImageAnalysis.Analyzer analyzer = mSubscribedAnalyzer.get();
- if (analyzer != null) {
- analyzer.analyze(imageProxy, mRelativeRotation.get());
- }
+ analyzeImage(imageProxy);
} finally {
finishImage(imageProxy);
mBackgroundExecutor.execute(new Runnable() {
@@ -168,7 +152,7 @@
}
synchronized void finishImage(ImageProxy imageProxy) {
- if (mIsClosed.get()) {
+ if (isClosed()) {
return;
}
mFinishedImageTimestamp.set(imageProxy.getTimestamp());
diff --git a/camera/camera-core/src/main/java/androidx/camera/core/ImageCapture.java b/camera/camera-core/src/main/java/androidx/camera/core/ImageCapture.java
index 23cc100..8d30323 100644
--- a/camera/camera-core/src/main/java/androidx/camera/core/ImageCapture.java
+++ b/camera/camera-core/src/main/java/androidx/camera/core/ImageCapture.java
@@ -408,17 +408,17 @@
@Override
public void onError(
ImageSaver.SaveError error, String message, @Nullable Throwable cause) {
- UseCaseError useCaseError = UseCaseError.UNKNOWN_ERROR;
+ ImageCaptureError imageCaptureError = ImageCaptureError.UNKNOWN_ERROR;
switch (error) {
case FILE_IO_FAILED:
- useCaseError = UseCaseError.FILE_IO_ERROR;
+ imageCaptureError = ImageCaptureError.FILE_IO_ERROR;
break;
default:
- // Keep the useCaseError as UNKNOWN_ERROR
+ // Keep the imageCaptureError as UNKNOWN_ERROR
break;
}
- imageSavedListener.onError(useCaseError, message, cause);
+ imageSavedListener.onError(imageCaptureError, message, cause);
}
};
@@ -444,7 +444,8 @@
@Override
public void onError(
- UseCaseError error, String message, @Nullable Throwable cause) {
+ @NonNull ImageCaptureError error, @NonNull String message,
+ @Nullable Throwable cause) {
imageSavedListener.onError(error, message, cause);
}
};
@@ -548,7 +549,8 @@
ImageCaptureRequest request =
mImageCaptureRequests.poll();
if (request != null) {
- request.callbackError(UseCaseError.UNKNOWN_ERROR,
+ request.callbackError(
+ ImageCaptureError.UNKNOWN_ERROR,
(error != null) ? error.getMessage()
: "Unknown error", error);
// Handle the next request.
@@ -564,6 +566,7 @@
mExecutor);
}
+ @NonNull
@Override
public String toString() {
return TAG + ":" + getName();
@@ -1010,9 +1013,9 @@
* ImageCapture#takePicture(OnImageCapturedListener)}).
*
* <p>This is a parameter sent to the error callback functions set in listeners such as {@link
- * ImageCapture.OnImageSavedListener#onError(UseCaseError, String, Throwable)}.
+ * ImageCapture.OnImageSavedListener#onError(ImageCaptureError, String, Throwable)}.
*/
- public enum UseCaseError {
+ public enum ImageCaptureError {
/**
* An unknown error occurred.
*
@@ -1051,7 +1054,7 @@
/** Called when an error occurs while attempting to save an image. */
void onError(
- @NonNull UseCaseError useCaseError,
+ @NonNull ImageCaptureError imageCaptureError,
@NonNull String message,
@Nullable Throwable cause);
}
@@ -1093,7 +1096,8 @@
/** Callback for when an error occurred during image capture. */
public void onError(
- UseCaseError useCaseError, String message, @Nullable Throwable cause) {
+ @NonNull ImageCaptureError imageCaptureError, @NonNull String message,
+ @Nullable Throwable cause) {
}
}
@@ -1341,13 +1345,13 @@
mListener.onCaptureSuccess(image, mRotationDegrees);
}
- void callbackError(final UseCaseError useCaseError, final String message,
+ void callbackError(final ImageCaptureError imageCaptureError, final String message,
final Throwable cause) {
if (mHandler != null && Looper.myLooper() != mHandler.getLooper()) {
boolean posted = mHandler.post(new Runnable() {
@Override
public void run() {
- ImageCaptureRequest.this.callbackError(useCaseError, message, cause);
+ ImageCaptureRequest.this.callbackError(imageCaptureError, message, cause);
}
});
if (!posted) {
@@ -1356,7 +1360,7 @@
return;
}
- mListener.onError(useCaseError, message, cause);
+ mListener.onError(imageCaptureError, message, cause);
}
}
}
diff --git a/camera/camera-core/src/main/java/androidx/camera/core/MeteringPoint.java b/camera/camera-core/src/main/java/androidx/camera/core/MeteringPoint.java
new file mode 100644
index 0000000..fcf01b5
--- /dev/null
+++ b/camera/camera-core/src/main/java/androidx/camera/core/MeteringPoint.java
@@ -0,0 +1,131 @@
+/*
+ * 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.camera.core;
+
+import android.util.Rational;
+
+import androidx.annotation.Nullable;
+import androidx.annotation.RestrictTo;
+
+/**
+ * A MeteringPoint used to specify a region in sensor coordinates for focusing and metering
+ * Purpose.
+ *
+ * <p>To create a {@link MeteringPoint}, apps have to use
+ * {@link DisplayOrientedMeteringPointFactory} or {@link SensorOrientedMeteringPointFactory}.
+ * The X/Y insides a MeteringPoint represents the normalized X/Y inside current crop region. If no
+ * crop region is set, the the whole sensor area is used. AreaSize represents the width and the
+ * height of the metering area and Weight can also be specified. By default, a MeteringPoint is
+ * mapped to the sensor coordinates using Preview aspect ratio. A custom FOV aspect ratio can be
+ * set if apps want to use aspect ratio other than Preview.
+ */
+public class MeteringPoint {
+ private float mNormalizedCropRegionX;
+ private float mNormalizedCropRegionY;
+ private float mSize;
+ private float mWeight;
+ @Nullable
+ private Rational mFOVAspectRatio; // null for preview aspect ratio.
+
+ /**
+ * Constructor is restricted for use within library.
+ *
+ * @param normalizedCropRegionX normalized X (ranging from 0 to 1) in current crop region.
+ * @param normalizedCropRegionY normalized Y (ranging from 0 to 1) in current crop region.
+ * @param size size of the MeteringPoint(ranging from 0 to 1). The value
+ * represents the percentage of current crop region width/height.
+ * @param weight weight of this metering point ranging from 0 to 1.
+ * @param fovAspectRatio if specified, use this aspect ratio. Otherwise use Preview's
+ * aspect
+ * ratio.
+ * @hide
+ */
+ @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
+ public MeteringPoint(float normalizedCropRegionX, float normalizedCropRegionY, float size,
+ float weight, @Nullable Rational fovAspectRatio) {
+ mNormalizedCropRegionX = normalizedCropRegionX;
+ mNormalizedCropRegionY = normalizedCropRegionY;
+ mSize = size;
+ mWeight = weight;
+ mFOVAspectRatio = fovAspectRatio;
+ }
+
+ /**
+ * Normalized crop region X (Ranging from 0 to 1)
+ */
+ public float getNormalizedCropRegionX() {
+ return mNormalizedCropRegionX;
+ }
+
+ /**
+ * Normalized crop region Y (Ranging from 0 to 1)
+ */
+ public float getNormalizedCropRegionY() {
+ return mNormalizedCropRegionY;
+ }
+
+ /**
+ * Size of the MeteringPoint(ranging from 0 to 1). The value represents the percentage of
+ * current crop region width/height
+ */
+ public float getSize() {
+ return mSize;
+ }
+
+ /**
+ * Weight of the MeteringPoint (Ranging from 0 to 1)
+ */
+ public float getWeight() {
+ return mWeight;
+ }
+
+ /**
+ * Sets the size of MeteringPoint (ranging from 0 to 1). It is the percentage of the sensor
+ * width/height (or cropRegion width/height if crop region is set)
+ *
+ * <p><pre>Metering Area width = size * cropRegion.width
+ * Metering Area height = size * cropRegion.height
+ * </pre>
+ *
+ */
+ public void setSize(float size) {
+ mSize = size;
+ }
+
+ /**
+ * Sets weight of this metering point (ranging from 0 to 1)
+ */
+ public void setWeight(float weight) {
+ mWeight = weight;
+ }
+
+ /**
+ * Set custom aspect ratio to be adjusted for final sensor coordinates.
+ */
+ public void setFOVAspectRatio(@Nullable Rational fovAspectRatio) {
+ mFOVAspectRatio = fovAspectRatio;
+ }
+
+
+ /**
+ * Get custom aspect ratio to be adjusted for final sensor coordinates.
+ */
+ @Nullable
+ public Rational getFOVAspectRatio() {
+ return mFOVAspectRatio;
+ }
+}
diff --git a/camera/camera-core/src/main/java/androidx/camera/core/MeteringPointFactory.java b/camera/camera-core/src/main/java/androidx/camera/core/MeteringPointFactory.java
new file mode 100644
index 0000000..f1cbe0c
--- /dev/null
+++ b/camera/camera-core/src/main/java/androidx/camera/core/MeteringPointFactory.java
@@ -0,0 +1,86 @@
+/*
+ * 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.camera.core;
+
+import android.graphics.PointF;
+import android.util.Rational;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+/**
+ * A Factory to create a {@link MeteringPoint}.
+ *
+ * <p>MeteringPointFactory implementations must extends this class and implement
+ * translatePoint(float x, float y). Users can call createPoint(float x, float y) to create a
+ * {@link MeteringPoint} with default areaSize and weight. There is a variation of createPoint
+ * that accepts areaSize and weight as well.
+ */
+public abstract class MeteringPointFactory {
+ public static final float DEFAULT_AREASIZE = 0.15f;
+ public static final float DEFAULT_WEIGHT = 1.0f;
+ @Nullable
+ protected Rational mFOVAspectRatio = null; // null for using Preview aspect ratio.
+
+ /**
+ * Translates a logical x/y into the normalized crop region x/y.
+ *
+ * <p>The logical x/y is with respect to related to the implementations. Implementations specify
+ * the logical width/height and define the orientation of the area. Some are sensor-oriented and
+ * some are display-oriented. The logical x/y is the position from the area defined by the width
+ * , height and the orientation.
+ *
+ * Implementation must implement this method for coordinates translation.
+ *
+ * @param x the logical x to be translated.
+ * @param y the logical y to be translated.
+ * @return a {@link PointF} consisting of translated normalized crop region x/y,
+ */
+ @NonNull
+ protected abstract PointF translatePoint(float x, float y);
+
+ /**
+ * Creates a {@link MeteringPoint} by x, y.
+ *
+ * <p>The x/y is the position from the area defined by the width, height and the orientation in
+ * specific {@link MeteringPointFactory} implementation.
+ */
+ @NonNull
+ public final MeteringPoint createPoint(float x, float y) {
+ return createPoint(x, y, DEFAULT_AREASIZE, DEFAULT_WEIGHT);
+ }
+
+ /**
+ * Creates a {@link MeteringPoint} by x , y , areaSize and weight.
+ *
+ * <p>The x/y is the position from the area defined by the width, height and the orientation in
+ * specific {@link MeteringPointFactory} implementation.
+ *
+ * @param x the logical x to be translated
+ * @param y the logical y to be translated
+ * @param size size of the point. The value is ranging from 0 to 1 meaning the
+ * percentage of crop region width/height.
+ * @param weight weight of metering region ranging from 0 to 1.
+ * @return A {@link MeteringPoint} that is translated into normalized crop region x/y.
+ */
+ @NonNull
+ public final MeteringPoint createPoint(float x, float y, float size, float weight) {
+ PointF translatedXY = translatePoint(x, y);
+ return new MeteringPoint(translatedXY.x, translatedXY.y, size, weight,
+ mFOVAspectRatio);
+ }
+}
diff --git a/camera/camera-core/src/main/java/androidx/camera/core/OnFocusListener.java b/camera/camera-core/src/main/java/androidx/camera/core/OnFocusListener.java
deleted file mode 100644
index 1c1350e..0000000
--- a/camera/camera-core/src/main/java/androidx/camera/core/OnFocusListener.java
+++ /dev/null
@@ -1,31 +0,0 @@
-/*
- * Copyright (C) 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.camera.core;
-
-import android.graphics.Rect;
-
-/** Listener called when focus scan has completed. */
-public interface OnFocusListener {
- /** Callback when focus has been locked. */
- void onFocusLocked(Rect afRect);
-
- /** Callback when unable to acquire focus. */
- void onFocusUnableToLock(Rect afRect);
-
- /** Callback when timeout is reached and af state haven't settled. */
- void onFocusTimedOut(Rect afRect);
-}
diff --git a/camera/camera-core/src/main/java/androidx/camera/core/Preview.java b/camera/camera-core/src/main/java/androidx/camera/core/Preview.java
index abc44f7..dc4dfd6 100644
--- a/camera/camera-core/src/main/java/androidx/camera/core/Preview.java
+++ b/camera/camera-core/src/main/java/androidx/camera/core/Preview.java
@@ -36,9 +36,9 @@
import androidx.annotation.UiThread;
import androidx.camera.core.CameraX.LensFacing;
import androidx.camera.core.ImageOutputConfig.RotationValue;
-import androidx.camera.core.impl.utils.executor.CameraXExecutors;
import com.google.auto.value.AutoValue;
+import com.google.common.util.concurrent.ListenableFuture;
import java.util.Map;
import java.util.Objects;
@@ -240,45 +240,6 @@
}
/**
- * Adjusts the preview according to the properties in some local regions.
- *
- * <p>The auto-focus (AF) and auto-exposure (AE) properties will be recalculated from the local
- * regions.
- *
- * <p>Dimensions of the sensor coordinate frame can be found using Camera2.
- *
- * @param focus rectangle with dimensions in sensor coordinate frame for focus
- * @param metering rectangle with dimensions in sensor coordinate frame for metering
- */
- public void focus(Rect focus, Rect metering) {
- focus(focus, metering, null);
- }
-
- /**
- * Adjusts the preview according to the properties in some local regions with a callback
- * called once focus scan has completed.
- *
- * <p>The auto-focus (AF) and auto-exposure (AE) properties will be recalculated from the local
- * regions.
- *
- * <p>Dimensions of the sensor coordinate frame can be found using Camera2.
- *
- * @param focus rectangle with dimensions in sensor coordinate frame for focus
- * @param metering rectangle with dimensions in sensor coordinate frame for metering
- * @param listener listener for when focus has completed
- */
- public void focus(Rect focus, Rect metering, @Nullable OnFocusListener listener) {
- // If the listener is not null, we will call it back on the main thread.
- if (listener == null) {
- getCurrentCameraControl().focus(focus, metering);
- } else {
- getCurrentCameraControl().focus(focus, metering, CameraXExecutors.mainThreadExecutor(),
- listener);
- }
-
- }
-
- /**
* Adjusts the preview to zoom to a local region.
*
* <p>Setting the zoom is equivalent to setting a scalar crop region (digital zoom), and zoom
@@ -341,6 +302,7 @@
}
}
+ @NonNull
@Override
public String toString() {
return TAG + ":" + getName();
@@ -504,13 +466,40 @@
UNKNOWN_ERROR
}
- /** A listener of {@link PreviewOutput}. */
+ /**
+ * A listener of {@link PreviewOutput}.
+ *
+ * TODO(b/117519540): Mark as deprecated once PreviewSurfaceCallback is ready.
+ */
public interface OnPreviewOutputUpdateListener {
/** Callback when PreviewOutput has been updated. */
void onUpdated(PreviewOutput output);
}
/**
+ * A callback to access the Preview Surface.
+ */
+ public interface PreviewSurfaceCallback {
+
+ /**
+ * Get preview output Surface with the given resolution and format.
+ *
+ * <p> This is called when Preview needs a valid Surface. e.g. when the Preview is bound
+ * to lifecycle or the current Surface is no longer valid. When that happens, the
+ * implementation is required to create a Surface with the given resolution/format.
+ *
+ * <p> The Surface should be released after the use case is no longer active. If released
+ * prematurely, the implementation will be asked to provide a new Surface via this method.
+ *
+ * @param resolution the resolution required by CameraX.
+ * @param imageFormat the {@link ImageFormat} required by CameraX.
+ * @return A ListenableFuture that contains the user created Surface.
+ */
+ @NonNull
+ ListenableFuture<Surface> getSurface(@NonNull Size resolution, int imageFormat);
+ }
+
+ /**
* Provides a base static default configuration for the Preview
*
* <p>These values may be overridden by the implementation. They only provide a minimum set of
diff --git a/camera/camera-core/src/main/java/androidx/camera/core/SensorOrientedMeteringPointFactory.java b/camera/camera-core/src/main/java/androidx/camera/core/SensorOrientedMeteringPointFactory.java
new file mode 100644
index 0000000..dac6cff
--- /dev/null
+++ b/camera/camera-core/src/main/java/androidx/camera/core/SensorOrientedMeteringPointFactory.java
@@ -0,0 +1,116 @@
+/*
+ * 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.camera.core;
+
+import android.graphics.PointF;
+import android.util.Rational;
+import android.util.Size;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.lifecycle.LifecycleOwner;
+
+import java.util.Set;
+
+/**
+ * A {@link MeteringPointFactory} that can create {@link MeteringPoint} by sensor oriented x, y ,
+ * width and height.
+ *
+ * <p>This factory is suitable for apps that already have coordinates translated into sensor
+ * coordinates. It is also useful for apps that want to focus on something detected in
+ * {@link ImageAnalysis}. Apps can pass the {@link ImageAnalysis} instance for useCaseForFOV
+ * argument and CameraX will then adjust the final sensor coordinates by aspect ratio of
+ * ImageAnalysis.
+ */
+public class SensorOrientedMeteringPointFactory extends MeteringPointFactory {
+ /** the logical width of FoV in sensor orientation*/
+ private final float mWidth;
+ /** the logical height of FoV in sensor orientation */
+ private final float mHeight;
+
+ /**
+ * Creates the SensorOrientedMeteringPointFactory by width and height
+ *
+ * <p>The width/height is the logical width/height of the preview FoV in sensor orientation and
+ * X/Y is the logical XY inside the FOV. User can set the width and height to 1.0 which will
+ * make the XY the normalized coordinates [0..1].
+ *
+ * <p>By default, it will use active {@link Preview} as the FoV for final coordinates
+ * translation.
+ *
+ * @param width the logical width of FoV in sensor orientation
+ * @param height the logical height of FoV in sensor orientation
+ */
+ public SensorOrientedMeteringPointFactory(float width, float height) {
+ mWidth = width;
+ mHeight = height;
+ mFOVAspectRatio = null;
+ }
+
+ /**
+ * Creates the SensorOrientedMeteringPointFactory by width, height and useCaseForFOV.
+ *
+ * <p>The width/height is the logical width/height of the preview FoV in sensor orientation and
+ * X/Y is the logical XY inside the FOV. User can set the width and height to 1.0 which will
+ * make the XY the normalized coordinates [0..1].
+ *
+ * <p>useCaseForFOV is used to determine the FOV of this translation. This useCaseForFOV needs
+ * to be bound via {@link CameraX#bindToLifecycle(LifecycleOwner, UseCase...)} first. Otherwise
+ * it will throw a {@link IllegalStateException}
+ *
+ * @param width the logical width of FOV in sensor orientation.
+ * @param height the logical height of FOV in sensor orientation.
+ * @param useCaseForFOV the {@link UseCase} to be the FOV.
+ */
+ public SensorOrientedMeteringPointFactory(float width, float height,
+ @NonNull UseCase useCaseForFOV) {
+ mWidth = width;
+ mHeight = height;
+ mFOVAspectRatio = getUseCaseAspectRatio(useCaseForFOV);
+ }
+
+ @Nullable
+ private Rational getUseCaseAspectRatio(@Nullable UseCase useCase) {
+ if (useCase == null) {
+ return null;
+ }
+
+ Set<String> cameraIds = useCase.getAttachedCameraIds();
+ if (cameraIds.isEmpty()) {
+ throw new IllegalStateException("UseCase " + useCase + " is not bound.");
+ }
+
+ for (String id : cameraIds) {
+ Size resolution = useCase.getAttachedSurfaceResolution(id);
+ // Returns an aspect ratio of first found attachedSurfaceResolution.
+ return new Rational(resolution.getWidth(), resolution.getHeight());
+ }
+
+ return null;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ @NonNull
+ protected PointF translatePoint(float x, float y) {
+ PointF pt = new PointF(x / mWidth, y / mHeight);
+ return pt;
+ }
+
+}
diff --git a/camera/camera-core/src/main/java/androidx/camera/core/VideoCapture.java b/camera/camera-core/src/main/java/androidx/camera/core/VideoCapture.java
index f248a24..ead0f52 100644
--- a/camera/camera-core/src/main/java/androidx/camera/core/VideoCapture.java
+++ b/camera/camera-core/src/main/java/androidx/camera/core/VideoCapture.java
@@ -225,7 +225,7 @@
* called.
*
* <p>StartRecording() is asynchronous. User needs to check if any error occurs by setting the
- * {@link OnVideoSavedListener#onError(UseCaseError, String, Throwable)}.
+ * {@link OnVideoSavedListener#onError(VideoCaptureError, String, Throwable)}.
*
* @param saveLocation Location to save the video capture
* @param listener Listener to call for the recorded video
@@ -241,7 +241,7 @@
* called.
*
* <p>StartRecording() is asynchronous. User needs to check if any error occurs by setting the
- * {@link OnVideoSavedListener#onError(UseCaseError, String, Throwable)}.
+ * {@link OnVideoSavedListener#onError(VideoCaptureError, String, Throwable)}.
*
* @param saveLocation Location to save the video capture
* @param listener Listener to call for the recorded video
@@ -253,7 +253,8 @@
if (!mEndOfAudioVideoSignal.get()) {
listener.onError(
- UseCaseError.RECORDING_IN_PROGRESS, "It is still in video recording!", null);
+ VideoCaptureError.RECORDING_IN_PROGRESS, "It is still in video recording!",
+ null);
return;
}
@@ -261,7 +262,7 @@
// audioRecord start
mAudioRecorder.startRecording();
} catch (IllegalStateException e) {
- listener.onError(UseCaseError.ENCODER_ERROR, "AudioRecorder start fail", e);
+ listener.onError(VideoCaptureError.ENCODER_ERROR, "AudioRecorder start fail", e);
return;
}
@@ -277,7 +278,7 @@
} catch (IllegalStateException e) {
setupEncoder(getAttachedSurfaceResolution(cameraId));
- listener.onError(UseCaseError.ENCODER_ERROR, "Audio/Video encoder start fail", e);
+ listener.onError(VideoCaptureError.ENCODER_ERROR, "Audio/Video encoder start fail", e);
return;
}
@@ -309,7 +310,7 @@
}
} catch (IOException e) {
setupEncoder(getAttachedSurfaceResolution(cameraId));
- listener.onError(UseCaseError.MUXER_ERROR, "MediaMuxer creation failed!", e);
+ listener.onError(VideoCaptureError.MUXER_ERROR, "MediaMuxer creation failed!", e);
return;
}
@@ -344,8 +345,9 @@
* VideoCapture#startRecording(File, OnVideoSavedListener, Metadata)} is called.
*
* <p>stopRecording() is asynchronous API. User need to check if {@link
- * OnVideoSavedListener#onVideoSaved(File)} or {@link OnVideoSavedListener#onError(UseCaseError,
- * String, Throwable)} be called before startRecording.
+ * OnVideoSavedListener#onVideoSaved(File)} or
+ * {@link OnVideoSavedListener#onError(VideoCaptureError, String, Throwable)} be called
+ * before startRecording.
*/
public void stopRecording() {
Log.i(TAG, "stopRecording");
@@ -581,7 +583,7 @@
case MediaCodec.INFO_OUTPUT_FORMAT_CHANGED:
if (mMuxerStarted) {
videoSavedListener.onError(
- UseCaseError.ENCODER_ERROR,
+ VideoCaptureError.ENCODER_ERROR,
"Unexpected change in video encoding format.",
null);
errorOccurred = true;
@@ -611,7 +613,8 @@
Log.i(TAG, "videoEncoder stop");
mVideoEncoder.stop();
} catch (IllegalStateException e) {
- videoSavedListener.onError(UseCaseError.ENCODER_ERROR, "Video encoder stop failed!", e);
+ videoSavedListener.onError(VideoCaptureError.ENCODER_ERROR,
+ "Video encoder stop failed!", e);
errorOccurred = true;
}
@@ -627,7 +630,7 @@
}
}
} catch (IllegalStateException e) {
- videoSavedListener.onError(UseCaseError.MUXER_ERROR, "Muxer stop failed!", e);
+ videoSavedListener.onError(VideoCaptureError.MUXER_ERROR, "Muxer stop failed!", e);
errorOccurred = true;
}
@@ -702,13 +705,14 @@
mAudioRecorder.stop();
} catch (IllegalStateException e) {
videoSavedListener.onError(
- UseCaseError.ENCODER_ERROR, "Audio recorder stop failed!", e);
+ VideoCaptureError.ENCODER_ERROR, "Audio recorder stop failed!", e);
}
try {
mAudioEncoder.stop();
} catch (IllegalStateException e) {
- videoSavedListener.onError(UseCaseError.ENCODER_ERROR, "Audio encoder stop failed!", e);
+ videoSavedListener.onError(VideoCaptureError.ENCODER_ERROR,
+ "Audio encoder stop failed!", e);
}
Log.i(TAG, "Audio encode thread end");
@@ -823,11 +827,11 @@
* Describes the error that occurred during video capture operations.
*
* <p>This is a parameter sent to the error callback functions set in listeners such as {@link
- * VideoCapture.OnVideoSavedListener#onError(UseCaseError, String, Throwable)}.
+ * VideoCapture.OnVideoSavedListener#onError(VideoCaptureError, String, Throwable)}.
*
* <p>See message parameter in onError callback or log for more details.
*/
- public enum UseCaseError {
+ public enum VideoCaptureError {
/**
* An unknown error occurred.
*
@@ -850,10 +854,11 @@
/** Listener containing callbacks for video file I/O events. */
public interface OnVideoSavedListener {
/** Called when the video has been successfully saved. */
- void onVideoSaved(File file);
+ void onVideoSaved(@NonNull File file);
/** Called when an error occurs while attempting to save the video. */
- void onError(UseCaseError useCaseError, String message, @Nullable Throwable cause);
+ void onError(@NonNull VideoCaptureError videoCaptureError, @NonNull String message,
+ @Nullable Throwable cause);
}
/**
diff --git a/camera/camera-core/src/main/java/androidx/camera/core/impl/utils/executor/SequentialExecutor.java b/camera/camera-core/src/main/java/androidx/camera/core/impl/utils/executor/SequentialExecutor.java
index 67c284b..a90d1ae 100644
--- a/camera/camera-core/src/main/java/androidx/camera/core/impl/utils/executor/SequentialExecutor.java
+++ b/camera/camera-core/src/main/java/androidx/camera/core/impl/utils/executor/SequentialExecutor.java
@@ -73,7 +73,7 @@
/** Use {@link CameraXExecutors#newSequentialExecutor} */
SequentialExecutor(Executor executor) {
- this.mExecutor = Preconditions.checkNotNull(executor);
+ mExecutor = Preconditions.checkNotNull(executor);
}
/**
diff --git a/camera/camera-core/src/test/java/androidx/camera/core/CameraRepositoryTest.java b/camera/camera-core/src/test/java/androidx/camera/core/CameraRepositoryTest.java
new file mode 100644
index 0000000..b64bba8
--- /dev/null
+++ b/camera/camera-core/src/test/java/androidx/camera/core/CameraRepositoryTest.java
@@ -0,0 +1,113 @@
+/*
+ * 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.camera.core;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.os.Build;
+
+import androidx.camera.testing.fakes.FakeCameraFactory;
+import androidx.test.filters.SmallTest;
+
+import com.google.common.util.concurrent.ListenableFuture;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.robolectric.RobolectricTestRunner;
+import org.robolectric.annotation.Config;
+import org.robolectric.annotation.internal.DoNotInstrument;
+import org.robolectric.shadows.ShadowLooper;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Set;
+import java.util.concurrent.ExecutionException;
+
+@SmallTest
+@RunWith(RobolectricTestRunner.class)
+@DoNotInstrument
+@Config(minSdk = Build.VERSION_CODES.LOLLIPOP)
+public final class CameraRepositoryTest {
+
+ private CameraRepository mCameraRepository;
+
+ @Before
+ public void setUp() {
+ mCameraRepository = new CameraRepository();
+ mCameraRepository.init(new FakeCameraFactory());
+ }
+
+ @Test
+ public void cameraIdsCanBeAcquired() {
+ Set<String> cameraIds = mCameraRepository.getCameraIds();
+
+ assertThat(cameraIds).isNotEmpty();
+ }
+
+ @Test
+ public void cameraCanBeObtainedWithValidId() {
+ for (String cameraId : mCameraRepository.getCameraIds()) {
+ BaseCamera camera = mCameraRepository.getCamera(cameraId);
+
+ assertThat(camera).isNotNull();
+ }
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void cameraCannotBeObtainedWithInvalidId() {
+ // Should throw IllegalArgumentException
+ mCameraRepository.getCamera("no_such_id");
+ }
+
+ @Test
+ public void cameraIdsAreClearedAfterDeinit() {
+ Set<String> cameraIdsBefore = mCameraRepository.getCameraIds();
+ mCameraRepository.deinit();
+ Set<String> cameraIdsAfter = mCameraRepository.getCameraIds();
+
+ assertThat(cameraIdsBefore).isNotEmpty();
+ assertThat(cameraIdsAfter).isEmpty();
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void camerasAreClearedAfterDeinit() {
+ Set<String> cameraIds = mCameraRepository.getCameraIds();
+ String validId = cameraIds.iterator().next();
+ mCameraRepository.deinit();
+ mCameraRepository.getCamera(validId);
+ }
+
+ @Test
+ public void camerasAreReleasedByDeinit() throws ExecutionException, InterruptedException {
+ List<BaseCamera> cameras = new ArrayList<>();
+ for (String cameraId : mCameraRepository.getCameraIds()) {
+ cameras.add(mCameraRepository.getCamera(cameraId));
+ }
+
+ ListenableFuture<Void> deinitFuture = mCameraRepository.deinit();
+
+ // Needed since FakeCamera uses LiveDataObservable
+ ShadowLooper.runUiThreadTasks();
+
+ assertThat(deinitFuture.isDone()).isTrue();
+ for (BaseCamera camera : cameras) {
+ assertThat(camera.getCameraState().fetchData().get()).isEqualTo(
+ BaseCamera.State.RELEASED);
+ }
+ }
+}
diff --git a/camera/camera-core/src/test/java/androidx/camera/core/FocusMeteringActionTest.java b/camera/camera-core/src/test/java/androidx/camera/core/FocusMeteringActionTest.java
new file mode 100644
index 0000000..6f7bc0e
--- /dev/null
+++ b/camera/camera-core/src/test/java/androidx/camera/core/FocusMeteringActionTest.java
@@ -0,0 +1,319 @@
+/*
+ * 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.camera.core;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import androidx.camera.core.FocusMeteringAction.MeteringMode;
+import androidx.test.filters.SmallTest;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mockito;
+import org.robolectric.RobolectricTestRunner;
+import org.robolectric.annotation.internal.DoNotInstrument;
+
+import java.util.concurrent.Executor;
+import java.util.concurrent.TimeUnit;
+
+@SmallTest
+@RunWith(RobolectricTestRunner.class)
+@DoNotInstrument
+public class FocusMeteringActionTest {
+ private SensorOrientedMeteringPointFactory mPointFactory =
+ new SensorOrientedMeteringPointFactory(1.0f, 1.0f);
+
+ MeteringPoint mPoint1 = mPointFactory.createPoint(0, 0);
+ MeteringPoint mPoint2 = mPointFactory.createPoint(1, 1);
+ MeteringPoint mPoint3 = mPointFactory.createPoint(1, 0);
+
+ @Test
+ public void defaultBuilder_valueIsDefault() {
+ FocusMeteringAction action = FocusMeteringAction.Builder.from(mPoint1).build();
+
+ assertThat(action.getMeteringPointsAF()).containsExactly(mPoint1);
+ assertThat(action.getMeteringPointsAE()).containsExactly(mPoint1);
+ assertThat(action.getMeteringPointsAWB()).containsExactly(mPoint1);
+ assertThat(action.getAutoCancelDurationInMs()).isEqualTo(
+ FocusMeteringAction.DEFAULT_AUTOCANCEL_DURATION);
+ assertThat(action.getOnAutoFocusListener()).isNull();
+ }
+
+ @Test
+ public void fromPointWithAFAEAWB() {
+ FocusMeteringAction action = FocusMeteringAction.Builder
+ .from(mPoint1, MeteringMode.AF_AE_AWB).build();
+ assertThat(action.getMeteringPointsAF()).containsExactly(mPoint1);
+ assertThat(action.getMeteringPointsAE()).containsExactly(mPoint1);
+ assertThat(action.getMeteringPointsAWB()).containsExactly(mPoint1);
+ }
+
+ @Test
+ public void fromPointWithAFAE() {
+ FocusMeteringAction action = FocusMeteringAction.Builder
+ .from(mPoint1, MeteringMode.AF_AE).build();
+ assertThat(action.getMeteringPointsAF()).containsExactly(mPoint1);
+ assertThat(action.getMeteringPointsAE()).containsExactly(mPoint1);
+ assertThat(action.getMeteringPointsAWB()).isEmpty();
+ }
+
+ @Test
+ public void fromPointWithAFAWB() {
+ FocusMeteringAction action = FocusMeteringAction.Builder
+ .from(mPoint1, MeteringMode.AF_AWB).build();
+ assertThat(action.getMeteringPointsAF()).containsExactly(mPoint1);
+ assertThat(action.getMeteringPointsAE()).isEmpty();
+ assertThat(action.getMeteringPointsAWB()).containsExactly(mPoint1);
+ }
+
+ @Test
+ public void fromPointWithAEAWB() {
+ FocusMeteringAction action = FocusMeteringAction.Builder
+ .from(mPoint1, MeteringMode.AE_AWB).build();
+ assertThat(action.getMeteringPointsAF()).isEmpty();
+ assertThat(action.getMeteringPointsAE()).containsExactly(mPoint1);
+ assertThat(action.getMeteringPointsAWB()).containsExactly(mPoint1);
+ }
+
+ @Test
+ public void fromPointWithAF() {
+ FocusMeteringAction action = FocusMeteringAction.Builder
+ .from(mPoint1, MeteringMode.AF_ONLY).build();
+ assertThat(action.getMeteringPointsAF()).containsExactly(mPoint1);
+ assertThat(action.getMeteringPointsAE()).isEmpty();
+ assertThat(action.getMeteringPointsAWB()).isEmpty();
+ }
+
+ @Test
+ public void fromPointWithAE() {
+ FocusMeteringAction action = FocusMeteringAction.Builder
+ .from(mPoint1, MeteringMode.AE_ONLY).build();
+ assertThat(action.getMeteringPointsAF()).isEmpty();
+ assertThat(action.getMeteringPointsAE()).containsExactly(mPoint1);
+ assertThat(action.getMeteringPointsAWB()).isEmpty();
+ }
+
+ @Test
+ public void fromPointWithAWB() {
+ FocusMeteringAction action = FocusMeteringAction.Builder
+ .from(mPoint1, MeteringMode.AWB_ONLY).build();
+ assertThat(action.getMeteringPointsAF()).isEmpty();
+ assertThat(action.getMeteringPointsAE()).isEmpty();
+ assertThat(action.getMeteringPointsAWB()).containsExactly(mPoint1);
+ }
+
+ @Test
+ public void multiplePointsWithDefaultMeteringMode() {
+ FocusMeteringAction action = FocusMeteringAction.Builder.from(mPoint1)
+ .addPoint(mPoint2)
+ .addPoint(mPoint3)
+ .build();
+ assertThat(action.getMeteringPointsAF()).containsExactly(mPoint1, mPoint2, mPoint3);
+ assertThat(action.getMeteringPointsAE()).containsExactly(mPoint1, mPoint2, mPoint3);
+ assertThat(action.getMeteringPointsAWB()).containsExactly(mPoint1, mPoint2, mPoint3);
+ }
+
+ @Test
+ public void multiplePointsWithSameAF_AE_AWB() {
+ FocusMeteringAction action = FocusMeteringAction.Builder
+ .from(mPoint1, MeteringMode.AF_AE_AWB)
+ .addPoint(mPoint2, MeteringMode.AF_AE_AWB)
+ .addPoint(mPoint3, MeteringMode.AF_AE_AWB)
+ .build();
+ assertThat(action.getMeteringPointsAF()).containsExactly(mPoint1, mPoint2, mPoint3);
+ assertThat(action.getMeteringPointsAE()).containsExactly(mPoint1, mPoint2, mPoint3);
+ assertThat(action.getMeteringPointsAWB()).containsExactly(mPoint1, mPoint2, mPoint3);
+ }
+
+ @Test
+ public void multiplePointsWithSameAF_AE() {
+ FocusMeteringAction action = FocusMeteringAction.Builder
+ .from(mPoint1, MeteringMode.AF_AE)
+ .addPoint(mPoint2, MeteringMode.AF_AE)
+ .addPoint(mPoint3, MeteringMode.AF_AE)
+ .build();
+ assertThat(action.getMeteringPointsAF()).containsExactly(mPoint1, mPoint2, mPoint3);
+ assertThat(action.getMeteringPointsAE()).containsExactly(mPoint1, mPoint2, mPoint3);
+ assertThat(action.getMeteringPointsAWB()).isEmpty();
+ }
+
+ @Test
+ public void multiplePointsWithSameAE_AWB() {
+ FocusMeteringAction action = FocusMeteringAction.Builder
+ .from(mPoint1, MeteringMode.AE_AWB)
+ .addPoint(mPoint2, MeteringMode.AE_AWB)
+ .addPoint(mPoint3, MeteringMode.AE_AWB)
+ .build();
+ assertThat(action.getMeteringPointsAF()).isEmpty();
+ assertThat(action.getMeteringPointsAE()).containsExactly(mPoint1, mPoint2, mPoint3);
+ assertThat(action.getMeteringPointsAWB()).containsExactly(mPoint1, mPoint2, mPoint3);
+ }
+
+ @Test
+ public void multiplePointsWithSameAF_AWB() {
+ FocusMeteringAction action = FocusMeteringAction.Builder
+ .from(mPoint1, MeteringMode.AF_AWB)
+ .addPoint(mPoint2, MeteringMode.AF_AWB)
+ .addPoint(mPoint3, MeteringMode.AF_AWB)
+ .build();
+ assertThat(action.getMeteringPointsAF()).containsExactly(mPoint1, mPoint2, mPoint3);
+ assertThat(action.getMeteringPointsAE()).isEmpty();
+ assertThat(action.getMeteringPointsAWB()).containsExactly(mPoint1, mPoint2, mPoint3);
+ }
+
+ @Test
+ public void multiplePointsWithSameAWBOnly() {
+ FocusMeteringAction action = FocusMeteringAction.Builder.from(mPoint1,
+ MeteringMode.AWB_ONLY)
+ .addPoint(mPoint2, MeteringMode.AWB_ONLY)
+ .addPoint(mPoint3, MeteringMode.AWB_ONLY)
+ .build();
+ assertThat(action.getMeteringPointsAF()).isEmpty();
+ assertThat(action.getMeteringPointsAE()).isEmpty();
+ assertThat(action.getMeteringPointsAWB()).containsExactly(mPoint1, mPoint2, mPoint3);
+ }
+
+ @Test
+ public void multiplePointsWithSameAEOnly() {
+ FocusMeteringAction action = FocusMeteringAction.Builder.from(mPoint1,
+ MeteringMode.AE_ONLY)
+ .addPoint(mPoint2, MeteringMode.AE_ONLY)
+ .addPoint(mPoint3, MeteringMode.AE_ONLY)
+ .build();
+ assertThat(action.getMeteringPointsAF()).isEmpty();
+ assertThat(action.getMeteringPointsAE()).containsExactly(mPoint1, mPoint2, mPoint3);
+ assertThat(action.getMeteringPointsAWB()).isEmpty();
+ }
+
+ @Test
+ public void multiplePointsWithSameAFOnly() {
+ FocusMeteringAction action = FocusMeteringAction.Builder.from(mPoint1,
+ MeteringMode.AF_ONLY)
+ .addPoint(mPoint2, MeteringMode.AF_ONLY)
+ .addPoint(mPoint3, MeteringMode.AF_ONLY)
+ .build();
+ assertThat(action.getMeteringPointsAF()).containsExactly(mPoint1, mPoint2, mPoint3);
+ assertThat(action.getMeteringPointsAE()).isEmpty();
+ assertThat(action.getMeteringPointsAWB()).isEmpty();
+ }
+
+ @Test
+ public void multiplePointsWithAFOnly_AEOnly_AWBOnly() {
+ FocusMeteringAction action = FocusMeteringAction.Builder.from(mPoint1,
+ MeteringMode.AF_ONLY)
+ .addPoint(mPoint2, MeteringMode.AE_ONLY)
+ .addPoint(mPoint3, MeteringMode.AWB_ONLY)
+ .build();
+ assertThat(action.getMeteringPointsAF()).containsExactly(mPoint1);
+ assertThat(action.getMeteringPointsAE()).containsExactly(mPoint2);
+ assertThat(action.getMeteringPointsAWB()).containsExactly(mPoint3);
+ }
+
+ @Test
+ public void multiplePointsWithAFAE_AEAWB_AFAWB() {
+ FocusMeteringAction action = FocusMeteringAction.Builder.from(mPoint1,
+ MeteringMode.AF_AE)
+ .addPoint(mPoint2, MeteringMode.AE_AWB)
+ .addPoint(mPoint3, MeteringMode.AF_AWB)
+ .build();
+ assertThat(action.getMeteringPointsAF()).containsExactly(mPoint1, mPoint3);
+ assertThat(action.getMeteringPointsAE()).containsExactly(mPoint2, mPoint1);
+ assertThat(action.getMeteringPointsAWB()).containsExactly(mPoint3, mPoint2);
+ }
+
+ @Test
+ public void multiplePointsWithAFAEAWB_AEAWB_AFOnly() {
+ FocusMeteringAction action = FocusMeteringAction.Builder.from(mPoint1,
+ MeteringMode.AF_AE_AWB)
+ .addPoint(mPoint2, MeteringMode.AE_AWB)
+ .addPoint(mPoint3, MeteringMode.AF_ONLY)
+ .build();
+ assertThat(action.getMeteringPointsAF()).containsExactly(mPoint1, mPoint3);
+ assertThat(action.getMeteringPointsAE()).containsExactly(mPoint1, mPoint2);
+ assertThat(action.getMeteringPointsAWB()).containsExactly(mPoint1, mPoint2);
+ }
+
+ @Test
+ public void multiplePointsWithAEOnly_AFAWAEB_AEOnly() {
+ FocusMeteringAction action = FocusMeteringAction.Builder.from(mPoint1,
+ MeteringMode.AE_ONLY)
+ .addPoint(mPoint2, MeteringMode.AF_AE_AWB)
+ .addPoint(mPoint3, MeteringMode.AE_ONLY)
+ .build();
+ assertThat(action.getMeteringPointsAF()).containsExactly(mPoint2);
+ assertThat(action.getMeteringPointsAE()).containsExactly(mPoint1, mPoint2, mPoint3);
+ assertThat(action.getMeteringPointsAWB()).containsExactly(mPoint2);
+ }
+
+ @Test
+ public void onAutoFocusListenerIsSet() {
+ MeteringPoint mPoint1 = mPointFactory.createPoint(0, 0);
+ FocusMeteringAction.OnAutoFocusListener onAutoFocusListener =
+ new FocusMeteringAction.OnAutoFocusListener() {
+ @Override
+ public void onFocusCompleted(boolean isFocused) {
+
+ }
+ };
+
+ FocusMeteringAction action = FocusMeteringAction.Builder.from(mPoint1)
+ .setAutoFocusCallback(onAutoFocusListener)
+ .build();
+
+ assertThat(action.getOnAutoFocusListener()).isEqualTo(onAutoFocusListener);
+ }
+
+ @Test
+ public void onAutoFocusListenerAndExecutorIsSet() {
+ MeteringPoint mPoint1 = mPointFactory.createPoint(0, 0);
+ FocusMeteringAction.OnAutoFocusListener onAutoFocusListener =
+ Mockito.mock(FocusMeteringAction.OnAutoFocusListener.class);
+
+ Executor executor = Mockito.mock(Executor.class);
+
+ FocusMeteringAction action = FocusMeteringAction.Builder.from(mPoint1)
+ .setAutoFocusCallback(executor, onAutoFocusListener)
+ .build();
+
+ assertThat(action.getOnAutoFocusListener()).isEqualTo(onAutoFocusListener);
+ assertThat(action.getListenerExecutor()).isEqualTo(executor);
+ }
+
+ @Test
+ public void setAutoCancelDurationBySeconds() {
+ FocusMeteringAction action = FocusMeteringAction.Builder.from(mPoint1)
+ .setAutoCancelDuration(3, TimeUnit.SECONDS)
+ .build();
+ assertThat(action.getAutoCancelDurationInMs()).isEqualTo(3000);
+ }
+
+ @Test
+ public void setAutoCancelDurationByMinutes() {
+ FocusMeteringAction action = FocusMeteringAction.Builder.from(mPoint1)
+ .setAutoCancelDuration(2, TimeUnit.MINUTES)
+ .build();
+ assertThat(action.getAutoCancelDurationInMs()).isEqualTo(120000);
+ }
+
+ @Test
+ public void setAutoCancelDurationByMilliseconds() {
+ FocusMeteringAction action = FocusMeteringAction.Builder.from(mPoint1)
+ .setAutoCancelDuration(1500, TimeUnit.MILLISECONDS)
+ .build();
+ assertThat(action.getAutoCancelDurationInMs()).isEqualTo(1500);
+ }
+}
diff --git a/camera/camera-core/src/test/java/androidx/camera/core/ImageAnalysisTest.java b/camera/camera-core/src/test/java/androidx/camera/core/ImageAnalysisTest.java
index 2e22966..cec54b0 100644
--- a/camera/camera-core/src/test/java/androidx/camera/core/ImageAnalysisTest.java
+++ b/camera/camera-core/src/test/java/androidx/camera/core/ImageAnalysisTest.java
@@ -30,6 +30,7 @@
import androidx.camera.core.impl.utils.executor.CameraXExecutors;
import androidx.test.filters.MediumTest;
+import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -65,6 +66,7 @@
private Handler mBackgroundHandler;
private Executor mBackgroundExecutor;
private List<Image> mImagesReceived;
+ private ImageAnalysis mImageAnalysis;
@Before
public void setUp() {
@@ -82,6 +84,60 @@
ShadowImageReader.clear();
}
+ @After
+ public void tearDown() {
+ mImageAnalysis.clear();
+ mImagesReceived.clear();
+ }
+
+ @Test
+ public void nonBlockingAnalyzerClosed_imageNotAnalyzed() {
+ // Arrange.
+ setUpImageAnalysisWithMode(ImageAnalysis.ImageReaderMode.ACQUIRE_LATEST_IMAGE);
+
+ // Act.
+ // Receive images from camera feed.
+ ShadowImageReader.triggerCallbackWithImage(MOCK_IMAGE_1);
+ flushHandler(mBackgroundHandler);
+ ShadowImageReader.triggerCallbackWithImage(MOCK_IMAGE_2);
+ flushHandler(mBackgroundHandler);
+
+ // Assert.
+ // No image is received because callback handler is blocked.
+ assertThat(mImagesReceived).isEmpty();
+
+ // Flush callback handler and image1 is received.
+ flushHandler(mCallbackHandler);
+ assertThat(mImagesReceived).containsExactly(MOCK_IMAGE_1);
+
+ // Clear ImageAnalysis and flush both handlers. No more image should be received because
+ // it's closed.
+ mImageAnalysis.clear();
+ flushHandler(mBackgroundHandler);
+ flushHandler(mCallbackHandler);
+ assertThat(mImagesReceived).containsExactly(MOCK_IMAGE_1);
+ }
+
+ @Test
+ public void blockingAnalyzerClosed_imageNotAnalyzed() {
+ // Arrange.
+ setUpImageAnalysisWithMode(ImageAnalysis.ImageReaderMode.ACQUIRE_NEXT_IMAGE);
+
+ // Act.
+ // Receive images from camera feed.
+ ShadowImageReader.triggerCallbackWithImage(MOCK_IMAGE_1);
+ flushHandler(mBackgroundHandler);
+
+ // Assert.
+ // No image is received because callback handler is blocked.
+ assertThat(mImagesReceived).isEmpty();
+
+ // Flush callback handler and it's still empty because it's close.
+ mImageAnalysis.clear();
+ flushHandler(mCallbackHandler);
+ assertThat(mImagesReceived).isEmpty();
+ }
+
@Test
public void acquireLatestMode_doesNotBlock() {
// Arrange.
@@ -140,14 +196,14 @@
}
private void setUpImageAnalysisWithMode(ImageAnalysis.ImageReaderMode imageReaderMode) {
- ImageAnalysis imageAnalysis = new ImageAnalysis(new ImageAnalysisConfig.Builder()
+ mImageAnalysis = new ImageAnalysis(new ImageAnalysisConfig.Builder()
.setCallbackHandler(mCallbackHandler)
.setBackgroundExecutor(mBackgroundExecutor)
.setImageQueueDepth(QUEUE_DEPTH)
.setImageReaderMode(imageReaderMode)
.build());
- imageAnalysis.setAnalyzer(new ImageAnalysis.Analyzer() {
+ mImageAnalysis.setAnalyzer(new ImageAnalysis.Analyzer() {
@Override
public void analyze(ImageProxy image, int rotationDegrees) {
mImagesReceived.add(image.getImage());
@@ -156,7 +212,7 @@
Map<String, Size> suggestedResolutionMap = new HashMap<>();
suggestedResolutionMap.put(ShadowCameraX.DEFAULT_CAMERA_ID, DEFAULT_RESOLUTION);
- imageAnalysis.updateSuggestedResolution(suggestedResolutionMap);
+ mImageAnalysis.updateSuggestedResolution(suggestedResolutionMap);
}
/**
diff --git a/camera/camera-core/src/test/java/androidx/camera/core/ShadowImageReader.java b/camera/camera-core/src/test/java/androidx/camera/core/ShadowImageReader.java
index 40b067d..ed5d2c3 100644
--- a/camera/camera-core/src/test/java/androidx/camera/core/ShadowImageReader.java
+++ b/camera/camera-core/src/test/java/androidx/camera/core/ShadowImageReader.java
@@ -63,6 +63,11 @@
sShadowImageReader.getListener().onImageAvailable(sImageReader);
}
+ @Implementation
+ public void close() {
+ // no-op.
+ }
+
/**
* Clears incoming images.
*/
diff --git a/camera/camera-extensions/src/androidTest/java/androidx/camera/extensions/ExtensionTest.java b/camera/camera-extensions/src/androidTest/java/androidx/camera/extensions/ExtensionTest.java
index ded8b28..a33632d9 100644
--- a/camera/camera-extensions/src/androidTest/java/androidx/camera/extensions/ExtensionTest.java
+++ b/camera/camera-extensions/src/androidTest/java/androidx/camera/extensions/ExtensionTest.java
@@ -198,7 +198,7 @@
// Verify the take picture should not have any error happen.
verify(mockOnImageCapturedListener, never()).onError(
- any(ImageCapture.UseCaseError.class), anyString(), any(Throwable.class));
+ any(ImageCapture.ImageCaptureError.class), anyString(), any(Throwable.class));
}
@Test
diff --git a/camera/camera-extensions/src/main/java/androidx/camera/extensions/ImageCaptureExtender.java b/camera/camera-extensions/src/main/java/androidx/camera/extensions/ImageCaptureExtender.java
index 2d0e31e..0880084 100644
--- a/camera/camera-extensions/src/main/java/androidx/camera/extensions/ImageCaptureExtender.java
+++ b/camera/camera-extensions/src/main/java/androidx/camera/extensions/ImageCaptureExtender.java
@@ -50,7 +50,7 @@
/**
* Class for using an OEM provided extension on image capture.
*/
-abstract class ImageCaptureExtender {
+public abstract class ImageCaptureExtender {
static final Config.Option<EffectMode> OPTION_IMAGE_CAPTURE_EXTENDER_MODE =
Config.Option.create("camerax.extensions.imageCaptureExtender.mode", EffectMode.class);
diff --git a/camera/camera-extensions/src/main/java/androidx/camera/extensions/Version.java b/camera/camera-extensions/src/main/java/androidx/camera/extensions/Version.java
index e70e683..ce606be 100644
--- a/camera/camera-extensions/src/main/java/androidx/camera/extensions/Version.java
+++ b/camera/camera-extensions/src/main/java/androidx/camera/extensions/Version.java
@@ -18,6 +18,8 @@
import android.text.TextUtils;
+import androidx.annotation.NonNull;
+
import com.google.auto.value.AutoValue;
import java.math.BigInteger;
@@ -76,6 +78,7 @@
abstract String getDescription();
+ @NonNull
@Override
public String toString() {
StringBuilder sb = new StringBuilder(getMajor() + "." + getMinor() + "." + getPatch());
diff --git a/camera/camera-testing/build.gradle b/camera/camera-testing/build.gradle
index 396ab3c..718544f 100644
--- a/camera/camera-testing/build.gradle
+++ b/camera/camera-testing/build.gradle
@@ -30,9 +30,16 @@
implementation("androidx.annotation:annotation:1.0.0")
implementation(GUAVA_LISTENABLE_FUTURE)
implementation(project(":camera:camera-core"))
- implementation("androidx.core:core:1.1.0-rc01")
+ implementation("androidx.core:core:1.1.0")
implementation("androidx.concurrent:concurrent-futures:1.0.0-alpha03")
implementation(JUNIT)
+
+ testImplementation(ANDROIDX_TEST_CORE)
+ testImplementation(ANDROIDX_TEST_RUNNER)
+ testImplementation(JUNIT)
+ testImplementation(TRUTH)
+ testImplementation(ROBOLECTRIC)
+ testImplementation(MOCKITO_CORE)
}
android {
diff --git a/camera/camera-testing/lint-baseline.xml b/camera/camera-testing/lint-baseline.xml
index 0255bbf..1a680f2 100644
--- a/camera/camera-testing/lint-baseline.xml
+++ b/camera/camera-testing/lint-baseline.xml
@@ -609,72 +609,6 @@
<issue
id="UnknownNullness"
message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://android.github.io/kotlin-guides/interop.html#nullability-annotations"
- errorLine1=" public BaseCamera getCamera(String cameraId) {"
- errorLine2=" ~~~~~~~~~~">
- <location
- file="src/main/java/androidx/camera/testing/fakes/FakeCameraFactory.java"
- line="56"
- column="12"/>
- </issue>
-
- <issue
- id="UnknownNullness"
- message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://android.github.io/kotlin-guides/interop.html#nullability-annotations"
- errorLine1=" public BaseCamera getCamera(String cameraId) {"
- errorLine2=" ~~~~~~">
- <location
- file="src/main/java/androidx/camera/testing/fakes/FakeCameraFactory.java"
- line="56"
- column="33"/>
- </issue>
-
- <issue
- id="UnknownNullness"
- message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://android.github.io/kotlin-guides/interop.html#nullability-annotations"
- errorLine1=" public void insertCamera(String cameraId, BaseCamera camera) {"
- errorLine2=" ~~~~~~">
- <location
- file="src/main/java/androidx/camera/testing/fakes/FakeCameraFactory.java"
- line="74"
- column="30"/>
- </issue>
-
- <issue
- id="UnknownNullness"
- message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://android.github.io/kotlin-guides/interop.html#nullability-annotations"
- errorLine1=" public void insertCamera(String cameraId, BaseCamera camera) {"
- errorLine2=" ~~~~~~~~~~">
- <location
- file="src/main/java/androidx/camera/testing/fakes/FakeCameraFactory.java"
- line="74"
- column="47"/>
- </issue>
-
- <issue
- id="UnknownNullness"
- message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://android.github.io/kotlin-guides/interop.html#nullability-annotations"
- errorLine1=" public Set<String> getAvailableCameraIds() {"
- errorLine2=" ~~~~~~~~~~~">
- <location
- file="src/main/java/androidx/camera/testing/fakes/FakeCameraFactory.java"
- line="85"
- column="12"/>
- </issue>
-
- <issue
- id="UnknownNullness"
- message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://android.github.io/kotlin-guides/interop.html#nullability-annotations"
- errorLine1=" public String cameraIdForLensFacing(LensFacing lensFacing) {"
- errorLine2=" ~~~~~~~~~~">
- <location
- file="src/main/java/androidx/camera/testing/fakes/FakeCameraFactory.java"
- line="91"
- column="41"/>
- </issue>
-
- <issue
- id="UnknownNullness"
- message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://android.github.io/kotlin-guides/interop.html#nullability-annotations"
errorLine1=" public FakeCameraInfo(int sensorRotation, LensFacing lensFacing) {"
errorLine2=" ~~~~~~~~~~">
<location
diff --git a/camera/camera-testing/src/main/java/androidx/camera/testing/GLUtil.java b/camera/camera-testing/src/main/java/androidx/camera/testing/GLUtil.java
new file mode 100644
index 0000000..0e7b9b1
--- /dev/null
+++ b/camera/camera-testing/src/main/java/androidx/camera/testing/GLUtil.java
@@ -0,0 +1,121 @@
+/*
+ * 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.camera.testing;
+
+import static android.opengl.EGL14.EGL_CONTEXT_CLIENT_VERSION;
+import static android.opengl.EGL14.EGL_DEFAULT_DISPLAY;
+import static android.opengl.EGL14.EGL_HEIGHT;
+import static android.opengl.EGL14.EGL_NONE;
+import static android.opengl.EGL14.EGL_NO_CONTEXT;
+import static android.opengl.EGL14.EGL_NO_DISPLAY;
+import static android.opengl.EGL14.EGL_NO_SURFACE;
+import static android.opengl.EGL14.EGL_OPENGL_ES2_BIT;
+import static android.opengl.EGL14.EGL_PBUFFER_BIT;
+import static android.opengl.EGL14.EGL_RENDERABLE_TYPE;
+import static android.opengl.EGL14.EGL_SURFACE_TYPE;
+import static android.opengl.EGL14.EGL_WIDTH;
+
+import android.opengl.EGL14;
+import android.opengl.EGLConfig;
+import android.opengl.EGLContext;
+import android.opengl.EGLDisplay;
+import android.opengl.EGLSurface;
+import android.opengl.GLES20;
+
+/**
+ * Utility functions interacting with OpenGL.
+ *
+ * <p> These utility methods are meant only for testing purposes.
+ */
+public class GLUtil {
+
+ /** Set up a GL context so that GL calls requiring a context can be made. */
+ private static void setupGLContext() {
+ EGLDisplay eglDisplay = EGL14.eglGetDisplay(EGL_DEFAULT_DISPLAY);
+ if (eglDisplay == EGL_NO_DISPLAY) {
+ throw new RuntimeException("Unable to get EGL display.");
+ }
+ int[] majorVer = new int[1];
+ int majorOffset = 0;
+ int[] minorVer = new int[1];
+ int minorOffset = 0;
+ if (!EGL14.eglInitialize(eglDisplay, majorVer, majorOffset, minorVer, minorOffset)) {
+ throw new RuntimeException("Unable to initialize EGL.");
+ }
+
+ int[] configAttribs =
+ new int[]{
+ EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT,
+ EGL_SURFACE_TYPE, EGL_PBUFFER_BIT,
+ EGL_NONE
+ };
+ int configAttribsOffset = 0;
+ EGLConfig[] configs = new EGLConfig[1];
+ int configsOffset = 0;
+ int[] numConfigs = new int[1];
+ int numConfigsOffset = 0;
+ if (!EGL14.eglChooseConfig(
+ eglDisplay,
+ configAttribs,
+ configAttribsOffset,
+ configs,
+ configsOffset,
+ configs.length,
+ numConfigs,
+ numConfigsOffset)) {
+ throw new RuntimeException("No appropriate EGL config exists on device.");
+ }
+ EGLConfig eglConfig = configs[0];
+
+ // Use a 1x1 pbuffer as our surface
+ int[] pbufferAttribs =
+ new int[]{
+ EGL_WIDTH, 1,
+ EGL_HEIGHT, 1,
+ EGL_NONE
+ };
+ int pbufferAttribsOffset = 0;
+ EGLSurface eglPbuffer =
+ EGL14.eglCreatePbufferSurface(eglDisplay, eglConfig, pbufferAttribs,
+ pbufferAttribsOffset);
+ if (eglPbuffer == EGL_NO_SURFACE) {
+ throw new RuntimeException("Unable to create pbuffer surface.");
+ }
+
+ int[] contextAttribs = new int[]{EGL_CONTEXT_CLIENT_VERSION, 2, EGL_NONE};
+ int contextAttribsOffset = 0;
+ EGLContext eglContext =
+ EGL14.eglCreateContext(
+ eglDisplay, eglConfig, EGL_NO_CONTEXT, contextAttribs,
+ contextAttribsOffset);
+ if (eglContext == EGL_NO_CONTEXT) {
+ throw new RuntimeException("Unable to create EGL context.");
+ }
+
+ if (!EGL14.eglMakeCurrent(eglDisplay, eglPbuffer, eglPbuffer, eglContext)) {
+ throw new RuntimeException("Failed to make EGL context current.");
+ }
+ }
+
+ /** Get a texture id for GL. */
+ public static int getTexIdFromGLContext() {
+ setupGLContext();
+ int[] texIds = new int[1];
+ GLES20.glGenTextures(1, texIds, 0);
+ return texIds[0];
+ }
+}
diff --git a/camera/camera-testing/src/main/java/androidx/camera/testing/TimestampCaptureProcessor.java b/camera/camera-testing/src/main/java/androidx/camera/testing/TimestampCaptureProcessor.java
new file mode 100644
index 0000000..3681c4d1
--- /dev/null
+++ b/camera/camera-testing/src/main/java/androidx/camera/testing/TimestampCaptureProcessor.java
@@ -0,0 +1,107 @@
+/*
+ * 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.camera.testing;
+
+import android.media.Image;
+import android.util.Size;
+import android.view.Surface;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.RestrictTo;
+import androidx.camera.core.CaptureProcessor;
+import androidx.camera.core.ImageProxy;
+import androidx.camera.core.ImageProxyBundle;
+import androidx.core.util.Preconditions;
+
+import com.google.common.util.concurrent.ListenableFuture;
+
+import java.util.List;
+import java.util.concurrent.ExecutionException;
+
+/**
+ * A {@link CaptureProcessor} that wraps another CaptureProcessor and captures the timestamps of all
+ * the {@link ImageProxy} that are processed by it.
+ *
+ * <p>This class is used for testing of preview processing only. The expectation is that each
+ * {@link ImageProxyBundle} will only have a single {@link ImageProxy}.
+ *
+ * @hide
+ */
+@RestrictTo(RestrictTo.Scope.TESTS)
+public class TimestampCaptureProcessor implements CaptureProcessor {
+ private CaptureProcessor mCaptureProcessor;
+ private TimestampListener mTimestampListener;
+
+ /**
+ * @param captureProcessor The {@link CaptureProcessor} that is wrapped.
+ * @param timestampListener The listener which receives the timestamp of all
+ * {@link ImageProxy} which are processed.
+ */
+ public TimestampCaptureProcessor(@NonNull CaptureProcessor captureProcessor,
+ @NonNull TimestampListener timestampListener) {
+ mCaptureProcessor = captureProcessor;
+ mTimestampListener = timestampListener;
+ }
+
+ /**
+ * Interface for receiving the timestamps of all {@link ImageProxy} which are processed by the
+ * wrapped {@link CaptureProcessor}.
+ */
+ public interface TimestampListener {
+ /**
+ * Called whenever an {@link ImageProxy} is processed.
+ *
+ * @param timestamp The timestamp of the {@link ImageProxy} that is processed.
+ */
+ void onTimestampAvailable(long timestamp);
+ }
+
+ @Override
+ public void onOutputSurface(@NonNull Surface surface, int imageFormat) {
+ mCaptureProcessor.onOutputSurface(surface, imageFormat);
+ }
+
+ @Override
+ public void process(@NonNull ImageProxyBundle bundle) {
+ List<Integer> ids = bundle.getCaptureIds();
+ Preconditions.checkArgument(ids.size() == 1,
+ "Processing preview bundle must be 1, but found " + ids.size());
+
+ ListenableFuture<ImageProxy> imageProxyListenableFuture = bundle.getImageProxy(ids.get(0));
+ Preconditions.checkArgument(imageProxyListenableFuture.isDone());
+
+ try {
+ ImageProxy imageProxy = imageProxyListenableFuture.get();
+ Image image = imageProxy.getImage();
+ if (image == null) {
+ return;
+ }
+
+ // Send timestamp
+ mTimestampListener.onTimestampAvailable(image.getTimestamp());
+ mCaptureProcessor.process(bundle);
+ } catch (ExecutionException | InterruptedException e) {
+ // Intentionally empty. Only the ImageProxy which can be retrieved need to have its
+ // timestamp captured.
+ }
+ }
+
+ @Override
+ public void onResolutionUpdate(@NonNull Size size) {
+ mCaptureProcessor.onResolutionUpdate(size);
+ }
+}
diff --git a/camera/camera-testing/src/main/java/androidx/camera/testing/fakes/FakeActivity.java b/camera/camera-testing/src/main/java/androidx/camera/testing/fakes/FakeActivity.java
index 3becf04..3d873ca 100644
--- a/camera/camera-testing/src/main/java/androidx/camera/testing/fakes/FakeActivity.java
+++ b/camera/camera-testing/src/main/java/androidx/camera/testing/fakes/FakeActivity.java
@@ -17,22 +17,7 @@
package androidx.camera.testing.fakes;
import android.app.Activity;
-import android.os.Bundle;
-import androidx.camera.core.CameraX;
-
-/** A fake {@link Activity} that checks properties of the CameraX library. */
+/** A fake {@link Activity} that can be used in tests. */
public class FakeActivity extends Activity {
- private volatile boolean mIsCameraXInitializedAtOnCreate = false;
-
- @Override
- protected void onCreate(Bundle savedInstance) {
- super.onCreate(savedInstance);
- mIsCameraXInitializedAtOnCreate = CameraX.isInitialized();
- }
-
- /** Returns true if CameraX is initialized when {@link #onCreate(Bundle)} is called. */
- public boolean isCameraXInitializedAtOnCreate() {
- return mIsCameraXInitializedAtOnCreate;
- }
}
diff --git a/camera/camera-testing/src/main/java/androidx/camera/testing/fakes/FakeCamera.java b/camera/camera-testing/src/main/java/androidx/camera/testing/fakes/FakeCamera.java
index b6114cb..541eda4 100644
--- a/camera/camera-testing/src/main/java/androidx/camera/testing/fakes/FakeCamera.java
+++ b/camera/camera-testing/src/main/java/androidx/camera/testing/fakes/FakeCamera.java
@@ -20,6 +20,7 @@
import android.util.Log;
import android.view.Surface;
+import androidx.annotation.IntRange;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.camera.core.BaseCamera;
@@ -34,6 +35,7 @@
import androidx.camera.core.UseCaseAttachState;
import androidx.camera.core.impl.LiveDataObservable;
import androidx.camera.core.impl.utils.futures.Futures;
+import androidx.core.util.Preconditions;
import com.google.common.util.concurrent.ListenableFuture;
@@ -54,7 +56,8 @@
private final CameraInfo mCameraInfo;
private String mCameraId;
private UseCaseAttachState mUseCaseAttachState;
- private State mState = State.INITIALIZED;
+ private State mState = State.CLOSED;
+ private int mAvailableCameraCount = 1;
@Nullable
private SessionConfig mSessionConfig;
@@ -84,26 +87,66 @@
mUseCaseAttachState = new UseCaseAttachState(cameraId);
mCameraControlInternal = cameraControl == null ? new FakeCameraControl(this)
: cameraControl;
- mObservableState.postValue(BaseCamera.State.CLOSED);
+ mObservableState.postValue(State.CLOSED);
+ }
+
+ /**
+ * Sets the number of cameras that are available to open.
+ *
+ * <p>If this number is set to 0, then calling {@link #open()} will wait in a {@code
+ * PENDING_OPEN} state until the number is set to a value greater than 0 before entering an
+ * {@code OPEN} state.
+ *
+ * @param count An integer number greater than 0 representing the number of available cameras
+ * to open on this device.
+ */
+ public void setAvailableCameraCount(@IntRange(from = 0) int count) {
+ Preconditions.checkArgumentNonnegative(count);
+ mAvailableCameraCount = count;
+ if (mAvailableCameraCount > 0 && mState == State.PENDING_OPEN) {
+ open();
+ }
+ }
+
+ /**
+ * Retrieves the number of cameras available to open on this device, as seen by this camera.
+ *
+ * @return An integer number greater than 0 representing the number of available cameras to
+ * open on this device.
+ */
+ @IntRange(from = 0)
+ public int getAvailableCameraCount() {
+ return mAvailableCameraCount;
}
@Override
public void open() {
checkNotReleased();
- if (mState == State.INITIALIZED) {
- mState = State.OPENED;
- mObservableState.postValue(BaseCamera.State.OPEN);
+ if (mState == State.CLOSED || mState == State.PENDING_OPEN) {
+ if (mAvailableCameraCount > 0) {
+ mState = State.OPEN;
+ mObservableState.postValue(State.OPEN);
+ } else {
+ mState = State.PENDING_OPEN;
+ mObservableState.postValue(State.PENDING_OPEN);
+ }
}
}
@Override
public void close() {
checkNotReleased();
- if (mState == State.OPENED) {
- mSessionConfig = null;
- reconfigure();
- mState = State.INITIALIZED;
- mObservableState.postValue(BaseCamera.State.CLOSED);
+ switch (mState) {
+ case OPEN:
+ mSessionConfig = null;
+ reconfigure();
+ // fall through
+ case PENDING_OPEN:
+ mState = State.CLOSED;
+ mObservableState.postValue(State.CLOSED);
+ break;
+ default:
+ break;
}
}
@@ -111,12 +154,12 @@
@NonNull
public ListenableFuture<Void> release() {
checkNotReleased();
- if (mState == State.OPENED) {
+ if (mState == State.OPEN) {
close();
}
mState = State.RELEASED;
- mObservableState.postValue(BaseCamera.State.RELEASED);
+ mObservableState.postValue(State.RELEASED);
return Futures.immediateFuture(null);
}
@@ -244,7 +287,7 @@
return;
}
- if (mState != State.OPENED) {
+ if (mState != State.OPEN) {
Log.d(TAG, "CameraDevice is not opened");
return;
}
@@ -307,20 +350,4 @@
// notifySurfaceDetached calls.
mConfiguredDeferrableSurfaces.clear();
}
-
- enum State {
- /**
- * Stable state once the camera has been constructed.
- */
- INITIALIZED,
- /**
- * A stable state where the camera has been opened.
- */
- OPENED,
- /**
- * A stable state where the camera has been permanently closed.
- */
- RELEASED
- }
-
}
diff --git a/camera/camera-testing/src/main/java/androidx/camera/testing/fakes/FakeCameraControl.java b/camera/camera-testing/src/main/java/androidx/camera/testing/fakes/FakeCameraControl.java
index 683f5fa..37de5a7 100644
--- a/camera/camera-testing/src/main/java/androidx/camera/testing/fakes/FakeCameraControl.java
+++ b/camera/camera-testing/src/main/java/androidx/camera/testing/fakes/FakeCameraControl.java
@@ -16,7 +16,6 @@
package androidx.camera.testing.fakes;
-import android.annotation.SuppressLint;
import android.graphics.Rect;
import android.util.Log;
@@ -25,11 +24,10 @@
import androidx.camera.core.CameraControlInternal;
import androidx.camera.core.CaptureConfig;
import androidx.camera.core.FlashMode;
-import androidx.camera.core.OnFocusListener;
+import androidx.camera.core.FocusMeteringAction;
import androidx.camera.core.SessionConfig;
import java.util.List;
-import java.util.concurrent.Executor;
/**
* A fake implementation for the CameraControlInternal interface.
@@ -51,21 +49,6 @@
Log.d(TAG, "setCropRegion(" + crop + ")");
}
- @SuppressLint("LambdaLast") // Remove after https://issuetracker.google.com/135275901
- @Override
- public void focus(
- @NonNull final Rect focus,
- @NonNull final Rect metering,
- @NonNull final Executor listenerExecutor,
- @NonNull final OnFocusListener listener) {
- focus(focus, metering);
- }
-
- @Override
- public void focus(@NonNull Rect focus, @NonNull Rect metering) {
- Log.d(TAG, "focus(\n " + focus + ",\n " + metering + ")");
- }
-
@NonNull
@Override
public FlashMode getFlashMode() {
@@ -90,11 +73,6 @@
}
@Override
- public boolean isFocusLocked() {
- return false;
- }
-
- @Override
public void triggerAf() {
Log.d(TAG, "triggerAf()");
}
@@ -119,4 +97,12 @@
private void updateSessionConfig() {
mControlUpdateListener.onCameraControlUpdateSessionConfig(mSessionConfigBuilder.build());
}
+
+ @Override
+ public void startFocusAndMetering(FocusMeteringAction action) {
+ }
+
+ @Override
+ public void cancelFocusAndMetering() {
+ }
}
diff --git a/camera/camera-testing/src/main/java/androidx/camera/testing/fakes/FakeCameraFactory.java b/camera/camera-testing/src/main/java/androidx/camera/testing/fakes/FakeCameraFactory.java
index e470bda..f892b8c 100644
--- a/camera/camera-testing/src/main/java/androidx/camera/testing/fakes/FakeCameraFactory.java
+++ b/camera/camera-testing/src/main/java/androidx/camera/testing/fakes/FakeCameraFactory.java
@@ -59,7 +59,8 @@
}
@Override
- public BaseCamera getCamera(String cameraId) {
+ @NonNull
+ public BaseCamera getCamera(@NonNull String cameraId) {
if (mCameraIds.contains(cameraId)) {
BaseCamera camera = mCameraMap.get(cameraId);
if (camera == null) {
@@ -77,7 +78,7 @@
* @param cameraId Identifier to use for the camera.
* @param camera Camera implementation.
*/
- public void insertCamera(String cameraId, BaseCamera camera) {
+ public void insertCamera(@NonNull String cameraId, @NonNull BaseCamera camera) {
if (!mCameraIds.contains(cameraId)) {
HashSet<String> newCameraIds = new HashSet<>(mCameraIds);
newCameraIds.add(cameraId);
@@ -87,7 +88,26 @@
mCameraMap.put(cameraId, camera);
}
+ /**
+ * Inserts a camera with front camera id.
+ *
+ * @param camera Camera implementation.
+ */
+ public void insertFrontCamera(@NonNull BaseCamera camera) {
+ insertCamera(FRONT_ID, camera);
+ }
+
+ /**
+ * Inserts a camera with back camera id.
+ *
+ * @param camera Camera implementation.
+ */
+ public void insertBackCamera(@NonNull BaseCamera camera) {
+ insertCamera(BACK_ID, camera);
+ }
+
@Override
+ @NonNull
public Set<String> getAvailableCameraIds() {
return mCameraIds;
}
diff --git a/camera/camera-testing/src/test/java/androidx/camera/testing/fakes/FakeCameraDeviceSurfaceManagerTest.java b/camera/camera-testing/src/test/java/androidx/camera/testing/fakes/FakeCameraDeviceSurfaceManagerTest.java
new file mode 100644
index 0000000..5fc3689
--- /dev/null
+++ b/camera/camera-testing/src/test/java/androidx/camera/testing/fakes/FakeCameraDeviceSurfaceManagerTest.java
@@ -0,0 +1,90 @@
+/*
+ * 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.camera.testing.fakes;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.Mockito.mock;
+
+import android.os.Build;
+import android.util.Size;
+
+import androidx.camera.core.UseCase;
+import androidx.test.filters.SmallTest;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.robolectric.RobolectricTestRunner;
+import org.robolectric.annotation.Config;
+import org.robolectric.annotation.internal.DoNotInstrument;
+
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+
+@SmallTest
+@RunWith(RobolectricTestRunner.class)
+@DoNotInstrument
+@Config(minSdk = Build.VERSION_CODES.LOLLIPOP)
+public class FakeCameraDeviceSurfaceManagerTest {
+
+ private static final int FAKE_WIDTH0 = 400;
+ private static final int FAKE_HEIGHT0 = 300;
+
+ private static final int FAKE_WIDTH1 = 800;
+ private static final int FAKE_HEIGHT1 = 600;
+
+ private static final String FAKE_CAMERA_ID0 = "0";
+ private static final String FAKE_CAMERA_ID1 = "1";
+
+ private FakeCameraDeviceSurfaceManager mFakeCameraDeviceSurfaceManager;
+
+ private FakeUseCase mFakeUseCase;
+
+ private List<UseCase> mUseCaseList;
+
+ @Before
+ public void setUp() {
+ mFakeCameraDeviceSurfaceManager = new FakeCameraDeviceSurfaceManager();
+ mFakeUseCase = mock(FakeUseCase.class);
+
+ mFakeCameraDeviceSurfaceManager.setSuggestedResolution(FAKE_CAMERA_ID0,
+ mFakeUseCase.getClass(), new Size(FAKE_WIDTH0, FAKE_HEIGHT0));
+ mFakeCameraDeviceSurfaceManager.setSuggestedResolution(FAKE_CAMERA_ID1,
+ mFakeUseCase.getClass(), new Size(FAKE_WIDTH1, FAKE_HEIGHT1));
+
+ mUseCaseList = Collections.singletonList((UseCase) mFakeUseCase);
+ }
+
+ @Test
+ public void canRetrieveInsertedSuggestedResolutions() {
+ Map<UseCase, Size> suggestedSizesCamera0 =
+ mFakeCameraDeviceSurfaceManager.getSuggestedResolutions(FAKE_CAMERA_ID0,
+ Collections.<UseCase>emptyList(), mUseCaseList);
+ Map<UseCase, Size> suggestedSizesCamera1 =
+ mFakeCameraDeviceSurfaceManager.getSuggestedResolutions(FAKE_CAMERA_ID1,
+ Collections.<UseCase>emptyList(), mUseCaseList);
+
+ assertThat(suggestedSizesCamera0.get(mFakeUseCase)).isEqualTo(
+ new Size(FAKE_WIDTH0, FAKE_HEIGHT0));
+ assertThat(suggestedSizesCamera1.get(mFakeUseCase)).isEqualTo(
+ new Size(FAKE_WIDTH1, FAKE_HEIGHT1));
+
+ }
+
+}
diff --git a/camera/camera-testing/src/test/java/androidx/camera/testing/fakes/FakeCameraInfoTest.java b/camera/camera-testing/src/test/java/androidx/camera/testing/fakes/FakeCameraInfoTest.java
new file mode 100644
index 0000000..3c11b96
--- /dev/null
+++ b/camera/camera-testing/src/test/java/androidx/camera/testing/fakes/FakeCameraInfoTest.java
@@ -0,0 +1,59 @@
+/*
+ * 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.camera.testing.fakes;
+
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.os.Build;
+
+import androidx.camera.core.CameraX;
+import androidx.test.filters.SmallTest;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.robolectric.RobolectricTestRunner;
+import org.robolectric.annotation.Config;
+import org.robolectric.annotation.internal.DoNotInstrument;
+
+@SmallTest
+@RunWith(RobolectricTestRunner.class)
+@DoNotInstrument
+@Config(minSdk = Build.VERSION_CODES.LOLLIPOP)
+public final class FakeCameraInfoTest {
+
+ private static final int SENSOR_ROTATION_DEGREES = 90;
+ private static final CameraX.LensFacing LENS_FACING = CameraX.LensFacing.FRONT;
+
+ private FakeCameraInfo mFakeCameraInfo;
+
+ @Before
+ public void setUp() {
+ mFakeCameraInfo = new FakeCameraInfo(SENSOR_ROTATION_DEGREES, LENS_FACING);
+ }
+
+ @Test
+ public void canRetrieveLensFacingDirection() {
+ assertThat(mFakeCameraInfo.getLensFacing()).isSameInstanceAs(LENS_FACING);
+ }
+
+ @Test
+ public void canRetrieveSensorRotation() {
+ assertThat(mFakeCameraInfo.getSensorRotationDegrees()).isEqualTo(SENSOR_ROTATION_DEGREES);
+ }
+}
diff --git a/camera/camera-testing/src/test/java/androidx/camera/testing/fakes/FakeCameraTest.java b/camera/camera-testing/src/test/java/androidx/camera/testing/fakes/FakeCameraTest.java
new file mode 100644
index 0000000..8648c44a
--- /dev/null
+++ b/camera/camera-testing/src/test/java/androidx/camera/testing/fakes/FakeCameraTest.java
@@ -0,0 +1,136 @@
+/*
+ * 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.camera.testing.fakes;
+
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.os.Build;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.camera.core.BaseCamera;
+import androidx.camera.core.Observable;
+import androidx.camera.core.impl.utils.executor.CameraXExecutors;
+import androidx.test.filters.SmallTest;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.robolectric.RobolectricTestRunner;
+import org.robolectric.annotation.Config;
+import org.robolectric.annotation.internal.DoNotInstrument;
+import org.robolectric.shadows.ShadowLooper;
+
+@SmallTest
+@RunWith(RobolectricTestRunner.class)
+@DoNotInstrument
+@Config(minSdk = Build.VERSION_CODES.LOLLIPOP)
+public final class FakeCameraTest {
+
+ private FakeCamera mCamera;
+ private BaseCamera.State mLatestState;
+ private Observable.Observer<BaseCamera.State> mStateObserver =
+ new Observable.Observer<BaseCamera.State>() {
+ @Override
+ public void onNewData(@Nullable BaseCamera.State value) {
+ mLatestState = value;
+ }
+
+ @Override
+ public void onError(@NonNull Throwable t) {
+
+ }
+ };
+
+ @Before
+ public void setUp() {
+ mCamera = new FakeCamera();
+ mCamera.getCameraState().addObserver(CameraXExecutors.directExecutor(), mStateObserver);
+ ShadowLooper.runUiThreadTasks();
+ }
+
+ @After
+ public void tearDown() {
+ mCamera.getCameraState().removeObserver(mStateObserver);
+ }
+
+ @Test
+ public void cameraEntersOpenState_whenOpened() {
+ mCamera.open();
+ ShadowLooper.runUiThreadTasks();
+ assertThat(mLatestState).isEqualTo(BaseCamera.State.OPEN);
+ }
+
+ @Test
+ public void cameraIsInClosedState_whenInitialized() {
+ assertThat(mLatestState).isEqualTo(BaseCamera.State.CLOSED);
+ }
+
+ @Test
+ public void cameraEntersPendingState_whenOpened_withZeroCamerasAvailable() {
+ mCamera.setAvailableCameraCount(0);
+ mCamera.open();
+ ShadowLooper.runUiThreadTasks();
+ assertThat(mLatestState).isEqualTo(BaseCamera.State.PENDING_OPEN);
+ }
+
+ @Test
+ public void cameraEntersOpenState_whenCameraBecomesAvailable() {
+ mCamera.setAvailableCameraCount(0);
+ mCamera.open();
+ ShadowLooper.runUiThreadTasks();
+ BaseCamera.State intermediateState = mLatestState;
+ mCamera.setAvailableCameraCount(1);
+ ShadowLooper.runUiThreadTasks();
+
+ assertThat(intermediateState).isEqualTo(BaseCamera.State.PENDING_OPEN);
+ assertThat(mLatestState).isEqualTo(BaseCamera.State.OPEN);
+ }
+
+ @Test
+ public void cameraCanBeClosed_afterOpened() {
+ mCamera.open();
+ ShadowLooper.runUiThreadTasks();
+ BaseCamera.State intermediateState = mLatestState;
+ mCamera.close();
+ ShadowLooper.runUiThreadTasks();
+
+ assertThat(intermediateState).isEqualTo(BaseCamera.State.OPEN);
+ assertThat(mLatestState).isEqualTo(BaseCamera.State.CLOSED);
+ }
+
+ @Test
+ public void cameraCanBeClosed_fromPendingState() {
+ mCamera.setAvailableCameraCount(0);
+ mCamera.open();
+ ShadowLooper.runUiThreadTasks();
+ BaseCamera.State intermediateState = mLatestState;
+ mCamera.close();
+ ShadowLooper.runUiThreadTasks();
+
+ assertThat(intermediateState).isEqualTo(BaseCamera.State.PENDING_OPEN);
+ assertThat(mLatestState).isEqualTo(BaseCamera.State.CLOSED);
+ }
+
+ @Test
+ public void canSetAndRetrieveAvailableCameraCount() {
+ mCamera.setAvailableCameraCount(400);
+ assertThat(mCamera.getAvailableCameraCount()).isEqualTo(400);
+ }
+}
diff --git a/camera/camera-view/build.gradle b/camera/camera-view/build.gradle
index a222329..8c3f455 100644
--- a/camera/camera-view/build.gradle
+++ b/camera/camera-view/build.gradle
@@ -29,6 +29,15 @@
implementation("androidx.annotation:annotation:1.0.0")
api(project(":camera:camera-core"))
+ androidTestImplementation(project(":camera:camera-testing"))
+ androidTestImplementation(MOCKITO_CORE)
+ androidTestImplementation(ESPRESSO_CORE)
+ androidTestImplementation(ANDROIDX_TEST_EXT_JUNIT)
+ androidTestImplementation(ANDROIDX_TEST_CORE)
+ androidTestImplementation(ANDROIDX_TEST_RUNNER)
+ androidTestImplementation(ANDROIDX_TEST_RULES)
+ androidTestImplementation(TRUTH)
+ androidTestImplementation(project(":camera:camera-camera2"))
}
android {
defaultConfig {
diff --git a/camera/camera-view/src/androidTest/AndroidManifest.xml b/camera/camera-view/src/androidTest/AndroidManifest.xml
new file mode 100644
index 0000000..f6bc677
--- /dev/null
+++ b/camera/camera-view/src/androidTest/AndroidManifest.xml
@@ -0,0 +1,30 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 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.
+-->
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="androidx.camera.camera2.view">
+ <uses-permission android:name="android.permission.CAMERA" />
+ <uses-permission android:name="android.permission.RECORD_AUDIO" />
+ <application>
+ <activity
+ android:name="androidx.camera.testing.fakes.FakeActivity"
+ android:label="Fake Activity">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.LAUNCHER" />
+ </intent-filter>
+ </activity>
+ </application>
+</manifest>
diff --git a/camera/camera-view/src/androidTest/java/androidx/camera/view/TextureViewMeteringPointFactoryTest.java b/camera/camera-view/src/androidTest/java/androidx/camera/view/TextureViewMeteringPointFactoryTest.java
new file mode 100644
index 0000000..c928e91
--- /dev/null
+++ b/camera/camera-view/src/androidTest/java/androidx/camera/view/TextureViewMeteringPointFactoryTest.java
@@ -0,0 +1,302 @@
+/*
+ * 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.camera.view;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.junit.Assume.assumeTrue;
+
+import android.Manifest;
+import android.app.Activity;
+import android.content.Context;
+import android.graphics.SurfaceTexture;
+import android.hardware.camera2.CameraDevice;
+import android.view.TextureView;
+import android.view.View;
+import android.view.ViewGroup;
+
+import androidx.annotation.NonNull;
+import androidx.camera.camera2.Camera2AppConfig;
+import androidx.camera.camera2.Camera2Config;
+import androidx.camera.core.AppConfig;
+import androidx.camera.core.CameraX;
+import androidx.camera.core.DisplayOrientedMeteringPointFactory;
+import androidx.camera.core.MeteringPoint;
+import androidx.camera.core.MeteringPointFactory;
+import androidx.camera.core.Preview;
+import androidx.camera.core.PreviewConfig;
+import androidx.camera.testing.CameraUtil;
+import androidx.camera.testing.fakes.FakeActivity;
+import androidx.camera.testing.fakes.FakeLifecycleOwner;
+import androidx.test.core.app.ApplicationProvider;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.LargeTest;
+import androidx.test.rule.ActivityTestRule;
+import androidx.test.rule.GrantPermissionRule;
+
+import org.junit.After;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+@LargeTest
+@RunWith(AndroidJUnit4.class)
+public class TextureViewMeteringPointFactoryTest {
+ public static final float TOLERANCE = 0.000001f;
+ @Rule
+ public GrantPermissionRule mRuntimePermissionRule = GrantPermissionRule.grant(
+ Manifest.permission.CAMERA);
+ @Rule
+ public ActivityTestRule<FakeActivity> mActivityRule =
+ new ActivityTestRule<>(FakeActivity.class);
+
+ private void setContentView(View view) throws Throwable {
+ final Activity activity = mActivityRule.getActivity();
+ mActivityRule.runOnUiThread(new Runnable() {
+ @Override
+ public void run() {
+ activity.setContentView(view);
+ }
+ });
+ }
+
+ private static final int WAIT_FRAMECOUNT = 1;
+ private FakeLifecycleOwner mLifecycle;
+ private CountDownLatch mLatchForFrameReady;
+ private CountDownLatch mLatchForCameraClose;
+ private Context mContext;
+ private TextureView mTextureView;
+ private int mWidth;
+ private int mHeight;
+
+ @Before
+ public void setUp() throws Throwable {
+ assumeTrue(CameraUtil.deviceHasCamera());
+ mContext = ApplicationProvider.getApplicationContext();
+ AppConfig config = Camera2AppConfig.create(mContext);
+ CameraX.init(mContext, config);
+ mLifecycle = new FakeLifecycleOwner();
+ mLatchForFrameReady = new CountDownLatch(1);
+ mLatchForCameraClose = new CountDownLatch(1);
+ mTextureView = new TextureView(mContext);
+ setContentView(mTextureView);
+ }
+
+ @After
+ public void tearDown() throws InterruptedException {
+ CameraX.unbindAll();
+ mLatchForCameraClose.await(3, TimeUnit.SECONDS);
+ }
+
+ @Test
+ public void backCamera_translatedPoint_SameAsDisplayOriented() throws Throwable {
+ startAndWaitForCameraReady(CameraX.LensFacing.BACK);
+
+ TextureViewMeteringPointFactory factory = new TextureViewMeteringPointFactory(mTextureView);
+
+ // Creates the DisplayOrientedMeteringPointFactory with same width / height as TextureView
+ DisplayOrientedMeteringPointFactory displayFactory =
+ new DisplayOrientedMeteringPointFactory(mContext, CameraX.LensFacing.BACK,
+ mTextureView.getWidth(), mTextureView.getHeight());
+
+ // Uses DisplayOrientedMeteringPointFactory to verify if coordinates are correct.
+ // DisplayOrientedMeteringPointFactory has the same width / height as the TextureView and
+ // TextureViewMeteringPointFactory
+ assertFactoryOuputSamePoints(factory, displayFactory);
+ }
+
+ @Test
+ public void frontCamera_translatedPoint_SameAsDisplayOriented() throws Throwable {
+ startAndWaitForCameraReady(CameraX.LensFacing.FRONT);
+
+ TextureViewMeteringPointFactory factory = new TextureViewMeteringPointFactory(mTextureView);
+
+ // Creates the DisplayOrientedMeteringPointFactory with same width / height as TextureView
+ DisplayOrientedMeteringPointFactory displayFactory =
+ new DisplayOrientedMeteringPointFactory(mContext, CameraX.LensFacing.FRONT,
+ mWidth, mHeight);
+
+ // Uses DisplayOrientedMeteringPointFactory to verify if coordinates are correct.
+ // DisplayOrientedMeteringPointFactory has the same width / height as the TextureView and
+ // TextureViewMeteringPointFactory
+ assertFactoryOuputSamePoints(factory, displayFactory);
+
+ }
+
+
+ @Test
+ public void xy_OutOfRange() throws Throwable {
+ startAndWaitForCameraReady(CameraX.LensFacing.BACK);
+
+ TextureViewMeteringPointFactory factory = new TextureViewMeteringPointFactory(mTextureView);
+
+ // if x or y is not in range [0.. width or height], the output will not be valid normalized
+ // point ( value not in [0..1])
+ MeteringPoint pt1 = factory.createPoint(-1, 0);
+ assertThat(isValid(pt1)).isFalse();
+
+ pt1 = factory.createPoint(0, -1);
+ assertThat(isValid(pt1)).isFalse();
+
+ pt1 = factory.createPoint(mWidth + 1, 0);
+ assertThat(isValid(pt1)).isFalse();
+
+ pt1 = factory.createPoint(0, mHeight + 1);
+ assertThat(isValid(pt1)).isFalse();
+
+ pt1 = factory.createPoint(-1, mHeight + 1);
+ assertThat(isValid(pt1)).isFalse();
+
+ pt1 = factory.createPoint(mWidth + 1, mHeight + 1);
+ assertThat(isValid(pt1)).isFalse();
+
+ pt1 = factory.createPoint(-1, -1);
+ assertThat(isValid(pt1)).isFalse();
+
+ }
+
+ private boolean isValid(MeteringPoint pt) {
+ boolean xValid = pt.getNormalizedCropRegionX() >= 0 && pt.getNormalizedCropRegionX() <= 1f;
+ boolean yValid = pt.getNormalizedCropRegionY() >= 0 && pt.getNormalizedCropRegionY() <= 1f;
+ return xValid && yValid;
+ }
+
+ private void startAndWaitForCameraReady(CameraX.LensFacing lensFacing)
+ throws InterruptedException {
+ PreviewConfig.Builder previewConfigBuilder =
+ new PreviewConfig.Builder()
+ .setLensFacing(lensFacing);
+
+ new Camera2Config.Extender(previewConfigBuilder)
+ .setDeviceStateCallback(new CameraDevice.StateCallback() {
+ @Override
+ public void onOpened(@NonNull CameraDevice cameraDevice) {
+ }
+
+ @Override
+ public void onClosed(@NonNull CameraDevice camera) {
+ mLatchForCameraClose.countDown();
+ }
+
+ @Override
+ public void onDisconnected(@NonNull CameraDevice cameraDevice) {
+ }
+
+ @Override
+ public void onError(@NonNull CameraDevice cameraDevice, int i) {
+ }
+ });
+
+ Preview preview = new Preview(previewConfigBuilder.build());
+ preview.setOnPreviewOutputUpdateListener(
+ new Preview.OnPreviewOutputUpdateListener() {
+ @Override
+ public void onUpdated(Preview.PreviewOutput output) {
+ mActivityRule.getActivity().runOnUiThread(new Runnable() {
+ @Override
+ public void run() {
+ ViewGroup viewGroup = (ViewGroup) mTextureView.getParent();
+ viewGroup.removeView(mTextureView);
+ viewGroup.addView(mTextureView);
+
+ mTextureView.setSurfaceTexture(output.getSurfaceTexture());
+ output.getSurfaceTexture().setOnFrameAvailableListener(
+ new SurfaceTexture.OnFrameAvailableListener() {
+ int mFrameCount = 0;
+ @Override
+ public void onFrameAvailable(
+ SurfaceTexture surfaceTexture) {
+ mFrameCount++;
+ if (mFrameCount == WAIT_FRAMECOUNT) {
+ mLatchForFrameReady.countDown();
+ }
+ }
+ });
+ }
+ });
+ }
+ });
+
+ // SurfaceTexture#getTransformMatrix is initialized properly when camera starts to ouput.
+ CameraX.bindToLifecycle(mLifecycle, preview);
+ mLifecycle.startAndResume();
+
+ mLatchForFrameReady.await(3, TimeUnit.SECONDS);
+ mWidth = mTextureView.getWidth();
+ mHeight = mTextureView.getHeight();
+ }
+
+ private void assertFactoryOuputSamePoints(MeteringPointFactory factory1,
+ MeteringPointFactory factory2) {
+ MeteringPoint point1;
+ MeteringPoint point2;
+
+ // left-top corner
+ point1 = factory1.createPoint(0f, 0f);
+ point2 = factory2.createPoint(0f, 0f);
+ assertThat(isValid(point1)).isTrue();
+ Assert.assertEquals(point1.getNormalizedCropRegionX(), point2.getNormalizedCropRegionX(),
+ TOLERANCE);
+ Assert.assertEquals(point1.getNormalizedCropRegionY(), point2.getNormalizedCropRegionY(),
+ TOLERANCE);
+
+ // left-bottom corner
+ point1 = factory1.createPoint(0f, mHeight);
+ point2 = factory2.createPoint(0f, mHeight);
+
+ assertThat(isValid(point1)).isTrue();
+ Assert.assertEquals(point1.getNormalizedCropRegionX(), point2.getNormalizedCropRegionX(),
+ TOLERANCE);
+ Assert.assertEquals(point1.getNormalizedCropRegionY(), point2.getNormalizedCropRegionY(),
+ TOLERANCE);
+
+ // right-top corner
+ point1 = factory1.createPoint(mWidth, 0f);
+ point2 = factory2.createPoint(mWidth, 0f);
+
+ assertThat(isValid(point1)).isTrue();
+ Assert.assertEquals(point1.getNormalizedCropRegionX(), point2.getNormalizedCropRegionX(),
+ TOLERANCE);
+ Assert.assertEquals(point1.getNormalizedCropRegionY(), point2.getNormalizedCropRegionY(),
+ TOLERANCE);
+
+ // right-bottom corner
+ point1 = factory1.createPoint(mWidth, mHeight);
+ point2 = factory2.createPoint(mWidth, mHeight);
+
+ assertThat(isValid(point1)).isTrue();
+ Assert.assertEquals(point1.getNormalizedCropRegionX(), point2.getNormalizedCropRegionX(),
+ TOLERANCE);
+ Assert.assertEquals(point1.getNormalizedCropRegionY(), point2.getNormalizedCropRegionY(),
+ TOLERANCE);
+
+ // some random point
+ point1 = factory1.createPoint(100, 120);
+ point2 = factory2.createPoint(100, 120);
+
+ assertThat(isValid(point1)).isTrue();
+ Assert.assertEquals(point1.getNormalizedCropRegionX(), point2.getNormalizedCropRegionX(),
+ TOLERANCE);
+ Assert.assertEquals(point1.getNormalizedCropRegionY(), point2.getNormalizedCropRegionY(),
+ TOLERANCE);
+ }
+}
diff --git a/camera/camera-view/src/main/java/androidx/camera/view/CameraView.java b/camera/camera-view/src/main/java/androidx/camera/view/CameraView.java
index cf72ffb..3a0ade2 100644
--- a/camera/camera-view/src/main/java/androidx/camera/view/CameraView.java
+++ b/camera/camera-view/src/main/java/androidx/camera/view/CameraView.java
@@ -51,11 +51,16 @@
import androidx.annotation.RestrictTo;
import androidx.annotation.RestrictTo.Scope;
import androidx.annotation.UiThread;
+import androidx.camera.core.CameraInfoUnavailableException;
+import androidx.camera.core.CameraX;
import androidx.camera.core.CameraX.LensFacing;
import androidx.camera.core.FlashMode;
+import androidx.camera.core.FocusMeteringAction;
+import androidx.camera.core.FocusMeteringAction.MeteringMode;
import androidx.camera.core.ImageCapture.OnImageCapturedListener;
import androidx.camera.core.ImageCapture.OnImageSavedListener;
import androidx.camera.core.ImageProxy;
+import androidx.camera.core.MeteringPoint;
import androidx.camera.core.VideoCapture.OnVideoSavedListener;
import androidx.lifecycle.LifecycleOwner;
@@ -93,8 +98,6 @@
private static final int FLASH_MODE_AUTO = 1;
private static final int FLASH_MODE_ON = 2;
private static final int FLASH_MODE_OFF = 4;
- private final Rect mFocusingRect = new Rect();
- private final Rect mMeteringRect = new Rect();
// For tap-to-focus
private long mDownEventTimestamp;
// For pinch-to-zoom
@@ -121,8 +124,7 @@
private ScaleType mScaleType = ScaleType.CENTER_CROP;
// For accessibility event
private MotionEvent mUpEvent;
- private @Nullable
- Paint mLayerPaint;
+ private @Nullable Paint mLayerPaint;
public CameraView(Context context) {
this(context, null);
@@ -705,20 +707,6 @@
return mCameraModule.getLensFacing();
}
- /**
- * Focuses the camera on the given area.
- *
- * <p>Sets the focus and exposure metering rectangles. Coordinates for both X and Y dimensions
- * are Limited from -1000 to 1000, where (0, 0) is the center of the image and the width/height
- * represent the values from -1000 to 1000.
- *
- * @param focus Area used to focus the camera.
- * @param metering Area used for exposure metering.
- */
- public void focus(Rect focus, Rect metering) {
- mCameraModule.focus(focus, metering);
- }
-
/** Gets the active flash strategy. */
public FlashMode getFlash() {
return mCameraModule.getFlash();
@@ -781,10 +769,21 @@
final float x = (mUpEvent != null) ? mUpEvent.getX() : getX() + getWidth() / 2f;
final float y = (mUpEvent != null) ? mUpEvent.getY() : getY() + getHeight() / 2f;
mUpEvent = null;
- calculateTapArea(mFocusingRect, x, y, 1f);
- calculateTapArea(mMeteringRect, x, y, 1.5f);
- if (area(mFocusingRect) > 0 && area(mMeteringRect) > 0) {
- focus(mFocusingRect, mMeteringRect);
+
+ TextureViewMeteringPointFactory pointFactory = new TextureViewMeteringPointFactory(
+ mCameraTextureView);
+ float afPointWidth = 1.0f / 6.0f; // 1/6 total area
+ float aePointWidth = afPointWidth * 1.5f;
+ MeteringPoint afPoint = pointFactory.createPoint(x, y, afPointWidth, 1.0f);
+ MeteringPoint aePoint = pointFactory.createPoint(x, y, aePointWidth, 1.0f);
+
+ try {
+ CameraX.getCameraControl(getCameraLensFacing()).startFocusAndMetering(
+ FocusMeteringAction.Builder.from(afPoint, MeteringMode.AF_ONLY)
+ .addPoint(aePoint, MeteringMode.AE_ONLY)
+ .build());
+ } catch (CameraInfoUnavailableException e) {
+ Log.d(TAG, "cannot access camera", e);
}
return true;
@@ -795,80 +794,6 @@
return rect.width() * rect.height();
}
- /** The area must be between -1000,-1000 and 1000,1000 */
- private void calculateTapArea(Rect rect, float x, float y, float coefficient) {
- int max = 1000;
- int min = -1000;
-
- // Default to 300 (1/6th the total area) and scale by the coefficient
- int areaSize = (int) (300 * coefficient);
-
- // Rotate the coordinates if the camera orientation is different
- int width = getWidth();
- int height = getHeight();
-
- // Compensate orientation as it's mirrored on preview for forward facing cameras
- boolean compensateForMirroring = (getCameraLensFacing() == LensFacing.FRONT);
- int relativeCameraOrientation = getRelativeCameraOrientation(compensateForMirroring);
- int temp;
- float tempf;
- switch (relativeCameraOrientation) {
- case 90:
- // Fall-through
- case 270:
- // We're horizontal. Swap width/height. Swap x/y.
- temp = width;
- //noinspection SuspiciousNameCombination
- width = height;
- height = temp;
-
- tempf = x;
- //noinspection SuspiciousNameCombination
- x = y;
- y = tempf;
- break;
- default:
- break;
- }
-
- switch (relativeCameraOrientation) {
- // Map to correct coordinates according to relativeCameraOrientation
- case 90:
- y = height - y;
- break;
- case 180:
- x = width - x;
- y = height - y;
- break;
- case 270:
- x = width - x;
- break;
- default:
- break;
- }
-
- // Swap x if it's a mirrored preview
- if (compensateForMirroring) {
- x = width - x;
- }
-
- // Grab the x, y position from within the View and normalize it to -1000 to 1000
- x = min + distance(max, min) * (x / width);
- y = min + distance(max, min) * (y / height);
-
- // Modify the rect to the bounding area
- rect.top = (int) y - areaSize / 2;
- rect.left = (int) x - areaSize / 2;
- rect.bottom = rect.top + areaSize;
- rect.right = rect.left + areaSize;
-
- // Cap at -1000 to 1000
- rect.top = rangeLimit(rect.top, max, min);
- rect.left = rangeLimit(rect.left, max, min);
- rect.bottom = rangeLimit(rect.bottom, max, min);
- rect.right = rangeLimit(rect.right, max, min);
- }
-
private int rangeLimit(int val, int max, int min) {
return Math.min(Math.max(val, min), max);
}
diff --git a/camera/camera-view/src/main/java/androidx/camera/view/CameraXModule.java b/camera/camera-view/src/main/java/androidx/camera/view/CameraXModule.java
index cab946e..3fc3a44 100644
--- a/camera/camera-view/src/main/java/androidx/camera/view/CameraXModule.java
+++ b/camera/camera-view/src/main/java/androidx/camera/view/CameraXModule.java
@@ -30,6 +30,7 @@
import android.util.Rational;
import android.util.Size;
+import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.RequiresPermission;
import androidx.annotation.UiThread;
@@ -350,19 +351,19 @@
file,
new VideoCapture.OnVideoSavedListener() {
@Override
- public void onVideoSaved(File savedFile) {
+ public void onVideoSaved(@NonNull File savedFile) {
mVideoIsRecording.set(false);
listener.onVideoSaved(savedFile);
}
@Override
public void onError(
- VideoCapture.UseCaseError useCaseError,
- String message,
+ @NonNull VideoCapture.VideoCaptureError videoCaptureError,
+ @NonNull String message,
@Nullable Throwable cause) {
mVideoIsRecording.set(false);
Log.e(TAG, message, cause);
- listener.onError(useCaseError, message, cause);
+ listener.onError(videoCaptureError, message, cause);
}
});
}
@@ -439,31 +440,6 @@
}
}
- public void focus(Rect focus, Rect metering) {
- if (mPreview == null) {
- // Nothing to focus on since we don't yet have a preview
- return;
- }
-
- Rect rescaledFocus;
- Rect rescaledMetering;
- try {
- Rect sensorRegion;
- if (mCropRegion != null) {
- sensorRegion = mCropRegion;
- } else {
- sensorRegion = getSensorSize(getActiveCamera());
- }
- rescaledFocus = rescaleViewRectToSensorRect(focus, sensorRegion);
- rescaledMetering = rescaleViewRectToSensorRect(metering, sensorRegion);
- } catch (Exception e) {
- Log.e(TAG, "Failed to rescale the focus and metering rectangles.", e);
- return;
- }
-
- mPreview.focus(rescaledFocus, rescaledMetering);
- }
-
public float getZoomLevel() {
return mZoomLevel;
}
diff --git a/camera/camera-view/src/main/java/androidx/camera/view/TextureViewMeteringPointFactory.java b/camera/camera-view/src/main/java/androidx/camera/view/TextureViewMeteringPointFactory.java
new file mode 100644
index 0000000..dab8a31
--- /dev/null
+++ b/camera/camera-view/src/main/java/androidx/camera/view/TextureViewMeteringPointFactory.java
@@ -0,0 +1,93 @@
+/*
+ * Copyright 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.camera.view;
+
+import android.graphics.Matrix;
+import android.graphics.PointF;
+import android.graphics.SurfaceTexture;
+import android.view.TextureView;
+
+import androidx.annotation.NonNull;
+import androidx.camera.core.MeteringPoint;
+import androidx.camera.core.MeteringPointFactory;
+
+/**
+ * A {@link MeteringPointFactory} for creating a {@link MeteringPoint} by {@link TextureView} and
+ * (x,y).
+ *
+ * <p>SurfaceTexture in TextureView could be cropped, scaled or rotated by
+ * {@link TextureView#getTransform(Matrix)}. This factory translates the (x, y) into the sensor
+ * crop region normalized (x,y) by this transform. {@link SurfaceTexture#getTransformMatrix} is
+ * also used during the translation. No lens facing information is required because
+ * {@link SurfaceTexture#getTransformMatrix} contains the necessary transformation corresponding
+ * to the lens face of current camera ouput.
+ */
+public class TextureViewMeteringPointFactory extends MeteringPointFactory {
+ private final TextureView mTextureView;
+
+ public TextureViewMeteringPointFactory(@NonNull TextureView textureView) {
+ mTextureView = textureView;
+ }
+
+ /**
+ * Translates a (x,y) from TextureView.
+ */
+ @NonNull
+ @Override
+ protected PointF translatePoint(float x, float y) {
+ Matrix transform = new Matrix();
+ mTextureView.getTransform(transform);
+
+ // applying reverse of TextureView#getTransform
+ Matrix inverse = new Matrix();
+ transform.invert(inverse);
+ float[] pt = new float[]{x, y};
+ inverse.mapPoints(pt);
+
+ // get SurfaceTexture#getTransformMatrix
+ float[] surfaceTextureMat = new float[16];
+ mTextureView.getSurfaceTexture().getTransformMatrix(surfaceTextureMat);
+
+ // convert SurfaceTexture#getTransformMatrix(4x4 column major 3D matrix) to
+ // android.graphics.Matrix(3x3 row major 2D matrix)
+ Matrix surfaceTextureTransform = glMatrixToGraphicsMatrix(surfaceTextureMat);
+
+ float[] pt2 = new float[2];
+ // convert to texture coordinates first.
+ pt2[0] = pt[0] / mTextureView.getWidth();
+ pt2[1] = (mTextureView.getHeight() - pt[1]) / mTextureView.getHeight();
+ surfaceTextureTransform.mapPoints(pt2);
+
+ return new PointF(pt2[0], pt2[1]);
+ }
+
+ private Matrix glMatrixToGraphicsMatrix(float[] glMatrix) {
+ float[] convert = new float[9];
+ convert[0] = glMatrix[0];
+ convert[1] = glMatrix[4];
+ convert[2] = glMatrix[12];
+ convert[3] = glMatrix[1];
+ convert[4] = glMatrix[5];
+ convert[5] = glMatrix[13];
+ convert[6] = glMatrix[3];
+ convert[7] = glMatrix[7];
+ convert[8] = glMatrix[15];
+ Matrix graphicsMatrix = new Matrix();
+ graphicsMatrix.setValues(convert);
+ return graphicsMatrix;
+ }
+}
diff --git a/camera/integration-tests/coretestapp/src/main/java/androidx/camera/integration/core/CameraXActivity.java b/camera/integration-tests/coretestapp/src/main/java/androidx/camera/integration/core/CameraXActivity.java
index ef7c6fc..5310c58 100644
--- a/camera/integration-tests/coretestapp/src/main/java/androidx/camera/integration/core/CameraXActivity.java
+++ b/camera/integration-tests/coretestapp/src/main/java/androidx/camera/integration/core/CameraXActivity.java
@@ -40,6 +40,7 @@
import android.widget.TextView;
import android.widget.Toast;
+import androidx.annotation.NonNull;
import androidx.annotation.VisibleForTesting;
import androidx.appcompat.app.AppCompatActivity;
import androidx.camera.core.CameraInfoUnavailableException;
@@ -491,7 +492,7 @@
+ ".jpg"),
new ImageCapture.OnImageSavedListener() {
@Override
- public void onImageSaved(File file) {
+ public void onImageSaved(@NonNull File file) {
Log.d(TAG, "Saved image to " + file);
if (!mImageSavedIdlingResource.isIdleNow()) {
mImageSavedIdlingResource.decrement();
@@ -511,8 +512,8 @@
@Override
public void onError(
- ImageCapture.UseCaseError useCaseError,
- String message,
+ @NonNull ImageCapture.ImageCaptureError error,
+ @NonNull String message,
Throwable cause) {
Log.e(TAG, "Failed to save image.", cause);
if (!mImageSavedIdlingResource.isIdleNow()) {
@@ -836,7 +837,7 @@
@Override
public void onRequestPermissionsResult(
- int requestCode, String[] permissions, int[] grantResults) {
+ int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
switch (requestCode) {
case PERMISSIONS_REQUEST_CODE: {
// If request is cancelled, the result arrays are empty.
diff --git a/camera/integration-tests/coretestapp/src/main/java/androidx/camera/integration/core/VideoFileSaver.java b/camera/integration-tests/coretestapp/src/main/java/androidx/camera/integration/core/VideoFileSaver.java
index 0e2a819..84c541d 100644
--- a/camera/integration-tests/coretestapp/src/main/java/androidx/camera/integration/core/VideoFileSaver.java
+++ b/camera/integration-tests/coretestapp/src/main/java/androidx/camera/integration/core/VideoFileSaver.java
@@ -19,10 +19,11 @@
import android.util.Log;
import androidx.annotation.GuardedBy;
+import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.camera.core.VideoCapture;
import androidx.camera.core.VideoCapture.OnVideoSavedListener;
-import androidx.camera.core.VideoCapture.UseCaseError;
+import androidx.camera.core.VideoCapture.VideoCaptureError;
import java.io.File;
import java.text.Format;
@@ -43,7 +44,7 @@
private boolean mIsSaving = false;
@Override
- public void onVideoSaved(File file) {
+ public void onVideoSaved(@NonNull File file) {
Log.d(TAG, "Saved file: " + file.getPath());
synchronized (mLock) {
@@ -52,9 +53,10 @@
}
@Override
- public void onError(UseCaseError useCaseError, String message, @Nullable Throwable cause) {
+ public void onError(@NonNull VideoCaptureError videoCaptureError, @NonNull String message,
+ @Nullable Throwable cause) {
- Log.e(TAG, "Error: " + useCaseError + ", " + message);
+ Log.e(TAG, "Error: " + videoCaptureError + ", " + message);
if (cause != null) {
Log.e(TAG, "Error cause: " + cause.getCause());
}
diff --git a/camera/integration-tests/extensionstestapp/build.gradle b/camera/integration-tests/extensionstestapp/build.gradle
index 86487fe..8027ca5 100644
--- a/camera/integration-tests/extensionstestapp/build.gradle
+++ b/camera/integration-tests/extensionstestapp/build.gradle
@@ -57,5 +57,6 @@
androidTestImplementation(ANDROIDX_TEST_UIAUTOMATOR)
androidTestImplementation(ESPRESSO_CORE)
androidTestImplementation(project(":camera:camera-testing"))
+ androidTestCompileOnly(project(":camera:camera-extensions-stub"))
}
diff --git a/camera/integration-tests/extensionstestapp/src/androidTest/java/androidx/camera/integration/extensions/PreviewProcessorTimestampTest.java b/camera/integration-tests/extensionstestapp/src/androidTest/java/androidx/camera/integration/extensions/PreviewProcessorTimestampTest.java
new file mode 100644
index 0000000..4e1c563
--- /dev/null
+++ b/camera/integration-tests/extensionstestapp/src/androidTest/java/androidx/camera/integration/extensions/PreviewProcessorTimestampTest.java
@@ -0,0 +1,320 @@
+/*
+ * 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.camera.integration.extensions;
+
+import static junit.framework.TestCase.assertEquals;
+import static junit.framework.TestCase.assertNotNull;
+
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assume.assumeNotNull;
+import static org.junit.Assume.assumeTrue;
+
+import android.Manifest;
+import android.content.Context;
+import android.graphics.SurfaceTexture;
+import android.hardware.camera2.CameraDevice;
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.os.Looper;
+
+import androidx.annotation.NonNull;
+import androidx.camera.camera2.Camera2AppConfig;
+import androidx.camera.camera2.Camera2Config;
+import androidx.camera.core.AppConfig;
+import androidx.camera.core.CameraX;
+import androidx.camera.core.CaptureProcessor;
+import androidx.camera.core.ImageAnalysisConfig;
+import androidx.camera.core.ImageCapture;
+import androidx.camera.core.ImageCaptureConfig;
+import androidx.camera.core.Preview;
+import androidx.camera.core.PreviewConfig;
+import androidx.camera.extensions.AutoImageCaptureExtender;
+import androidx.camera.extensions.AutoPreviewExtender;
+import androidx.camera.extensions.BeautyImageCaptureExtender;
+import androidx.camera.extensions.BeautyPreviewExtender;
+import androidx.camera.extensions.BokehImageCaptureExtender;
+import androidx.camera.extensions.BokehPreviewExtender;
+import androidx.camera.extensions.ExtensionsManager;
+import androidx.camera.extensions.HdrImageCaptureExtender;
+import androidx.camera.extensions.HdrPreviewExtender;
+import androidx.camera.extensions.ImageCaptureExtender;
+import androidx.camera.extensions.NightImageCaptureExtender;
+import androidx.camera.extensions.NightPreviewExtender;
+import androidx.camera.extensions.PreviewExtender;
+import androidx.camera.testing.GLUtil;
+import androidx.camera.testing.TimestampCaptureProcessor;
+import androidx.camera.testing.fakes.FakeLifecycleOwner;
+import androidx.test.core.app.ApplicationProvider;
+import androidx.test.filters.MediumTest;
+import androidx.test.rule.GrantPermissionRule;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.HashSet;
+import java.util.Set;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * Tests that the {@link androidx.camera.extensions.impl.PreviewImageProcessorImpl} properly
+ * populates the timestamp for the {@link SurfaceTexture}.
+ */
+@MediumTest
+@RunWith(Parameterized.class)
+public class PreviewProcessorTimestampTest {
+
+ @Rule
+ public GrantPermissionRule mRuntimePermissionRule = GrantPermissionRule.grant(
+ Manifest.permission.CAMERA);
+
+ private FakeLifecycleOwner mLifecycleOwner;
+ private CameraDevice.StateCallback mCameraStatusCallback;
+ private ExtensionsManager.EffectMode mEffectMode;
+ private CameraX.LensFacing mLensFacing;
+ private CountDownLatch mLatch;
+ private CountDownLatch mInputTimestampsLatch;
+ private CountDownLatch mOutputTimestampsLatch;
+ private CountDownLatch mSurfaceTextureLatch;
+ private Set<Long> mInputTimestamps = new HashSet<>();
+ private Set<Long> mOutputTimestamps = new HashSet<>();
+
+ TimestampCaptureProcessor.TimestampListener mTimestampListener;
+ SurfaceTexture.OnFrameAvailableListener mOnFrameAvailableListener;
+
+ private ImageCaptureConfig.Builder mImageCaptureConfigBuilder;
+ private PreviewConfig.Builder mPreviewConfigBuilder;
+ private ImageAnalysisConfig.Builder mImageAnalysisConfigBuilder;
+
+ @Parameterized.Parameters
+ public static Collection<Object[]> getParameters() {
+ return Arrays.asList(new Object[][]{
+ {ExtensionsManager.EffectMode.BOKEH, CameraX.LensFacing.FRONT},
+ {ExtensionsManager.EffectMode.BOKEH, CameraX.LensFacing.BACK},
+ {ExtensionsManager.EffectMode.HDR, CameraX.LensFacing.FRONT},
+ {ExtensionsManager.EffectMode.HDR, CameraX.LensFacing.BACK},
+ {ExtensionsManager.EffectMode.BEAUTY, CameraX.LensFacing.FRONT},
+ {ExtensionsManager.EffectMode.BEAUTY, CameraX.LensFacing.BACK},
+ {ExtensionsManager.EffectMode.NIGHT, CameraX.LensFacing.FRONT},
+ {ExtensionsManager.EffectMode.NIGHT, CameraX.LensFacing.BACK},
+ {ExtensionsManager.EffectMode.AUTO, CameraX.LensFacing.FRONT},
+ {ExtensionsManager.EffectMode.AUTO, CameraX.LensFacing.BACK}
+ });
+ }
+
+ public PreviewProcessorTimestampTest(ExtensionsManager.EffectMode effectMode,
+ CameraX.LensFacing lensFacing) {
+ mEffectMode = effectMode;
+ mLensFacing = lensFacing;
+ }
+
+ @Before
+ public void setUp() {
+ mProcessingHandlerThread =
+ new HandlerThread("Processing");
+ mProcessingHandlerThread.start();
+ mProcessingHandler = new Handler(mProcessingHandlerThread.getLooper());
+
+ assumeTrue(androidx.camera.testing.CameraUtil.deviceHasCamera());
+
+ Context context = ApplicationProvider.getApplicationContext();
+ AppConfig appConfig = Camera2AppConfig.create(context);
+ CameraX.init(context, appConfig);
+
+ mLifecycleOwner = new FakeLifecycleOwner();
+
+ mImageCaptureConfigBuilder = new ImageCaptureConfig.Builder();
+ mPreviewConfigBuilder = new PreviewConfig.Builder();
+ mImageAnalysisConfigBuilder = new ImageAnalysisConfig.Builder();
+ mCameraStatusCallback = new CameraDevice.StateCallback() {
+ @Override
+ public void onOpened(@NonNull CameraDevice camera) {
+ mLatch = new CountDownLatch(1);
+ }
+
+ @Override
+ public void onDisconnected(@NonNull CameraDevice camera) {
+
+ }
+
+ @Override
+ public void onError(@NonNull CameraDevice camera, int error) {
+
+ }
+
+ @Override
+ public void onClosed(@NonNull CameraDevice camera) {
+ mLatch.countDown();
+ }
+ };
+
+ mInputTimestampsLatch = new CountDownLatch(1);
+
+ mTimestampListener = new TimestampCaptureProcessor.TimestampListener() {
+ boolean mComplete = false;
+
+ @Override
+ public void onTimestampAvailable(long timestamp) {
+ if (mComplete) {
+ return;
+ }
+
+ mInputTimestamps.add(timestamp);
+ if (mInputTimestamps.size() > 10) {
+ mInputTimestampsLatch.countDown();
+ mComplete = true;
+ }
+ }
+ };
+
+ mOutputTimestampsLatch = new CountDownLatch(1);
+
+ mOnFrameAvailableListener = new SurfaceTexture.OnFrameAvailableListener() {
+ boolean mComplete = false;
+
+ @Override
+ public void onFrameAvailable(SurfaceTexture surfaceTexture) {
+ if (mComplete) {
+ return;
+ }
+ surfaceTexture.updateTexImage();
+
+ mOutputTimestamps.add(surfaceTexture.getTimestamp());
+ if (mOutputTimestamps.size() > 10) {
+ mOutputTimestampsLatch.countDown();
+ mComplete = true;
+ }
+ }
+ };
+
+ mSurfaceTextureLatch = new CountDownLatch(1);
+
+ new Camera2Config.Extender(mImageCaptureConfigBuilder).setDeviceStateCallback(
+ mCameraStatusCallback);
+ }
+
+ @After
+ public void cleanUp() throws InterruptedException {
+ if (mLatch != null) {
+ CameraX.unbindAll();
+
+ // Make sure camera was closed.
+ mLatch.await(3000, TimeUnit.MILLISECONDS);
+ }
+ }
+
+ private Handler mMainHandler = new Handler(Looper.getMainLooper());
+ private HandlerThread mProcessingHandlerThread;
+ private Handler mProcessingHandler;
+
+ @Test
+ public void timestampIsCorrect() throws InterruptedException {
+ assumeTrue(androidx.camera.testing.CameraUtil.hasCameraWithLensFacing(mLensFacing));
+ assumeTrue(ExtensionsManager.isExtensionAvailable(mEffectMode, mLensFacing));
+
+ enableExtension(mEffectMode, mLensFacing);
+
+ // To test bind/unbind and take picture.
+ ImageCapture imageCapture = new ImageCapture(mImageCaptureConfigBuilder.build());
+
+ PreviewConfig previewConfig = mPreviewConfigBuilder
+ .build();
+ CaptureProcessor previewCaptureProcessor = previewConfig.getCaptureProcessor(null);
+ assumeNotNull(previewCaptureProcessor);
+ mPreviewConfigBuilder.setCaptureProcessor(
+ new TimestampCaptureProcessor(previewCaptureProcessor, mTimestampListener));
+
+ Preview preview = new Preview(mPreviewConfigBuilder.build());
+
+ // To set the update listener and Preview will change to active state.
+ preview.setOnPreviewOutputUpdateListener(previewOutput -> {
+ mProcessingHandler.post(() -> previewOutput.getSurfaceTexture()
+ .attachToGLContext(GLUtil.getTexIdFromGLContext()));
+ previewOutput.getSurfaceTexture().setOnFrameAvailableListener(
+ mOnFrameAvailableListener, mProcessingHandler);
+
+ mSurfaceTextureLatch.countDown();
+ }
+ );
+
+ mMainHandler.post(() -> {
+ CameraX.bindToLifecycle(mLifecycleOwner, preview, imageCapture);
+
+ mLifecycleOwner.startAndResume();
+ });
+
+ assertTrue(mSurfaceTextureLatch.await(1000, TimeUnit.MILLISECONDS));
+
+ assertTrue(mInputTimestampsLatch.await(1000, TimeUnit.MILLISECONDS));
+ assertTrue(mOutputTimestampsLatch.await(1000, TimeUnit.MILLISECONDS));
+
+ assertEquals(mInputTimestamps, mOutputTimestamps);
+ }
+
+ /**
+ * To invoke the enableExtension() method for different effect.
+ */
+ private void enableExtension(ExtensionsManager.EffectMode effectMode,
+ CameraX.LensFacing lensFacing) {
+
+ mImageCaptureConfigBuilder.setLensFacing(lensFacing);
+ mPreviewConfigBuilder.setLensFacing(lensFacing);
+ mImageAnalysisConfigBuilder.setLensFacing(lensFacing);
+
+ ImageCaptureExtender imageCaptureExtender = null;
+ PreviewExtender previewExtender = null;
+
+ switch (effectMode) {
+ case HDR:
+ imageCaptureExtender = HdrImageCaptureExtender.create(mImageCaptureConfigBuilder);
+ previewExtender = HdrPreviewExtender.create(mPreviewConfigBuilder);
+ break;
+ case BOKEH:
+ imageCaptureExtender = BokehImageCaptureExtender.create(
+ mImageCaptureConfigBuilder);
+ previewExtender = BokehPreviewExtender.create(mPreviewConfigBuilder);
+ break;
+ case BEAUTY:
+ imageCaptureExtender = BeautyImageCaptureExtender.create(
+ mImageCaptureConfigBuilder);
+ previewExtender = BeautyPreviewExtender.create(mPreviewConfigBuilder);
+ break;
+ case NIGHT:
+ imageCaptureExtender = NightImageCaptureExtender.create(mImageCaptureConfigBuilder);
+ previewExtender = NightPreviewExtender.create(mPreviewConfigBuilder);
+ break;
+ case AUTO:
+ imageCaptureExtender = AutoImageCaptureExtender.create(mImageCaptureConfigBuilder);
+ previewExtender = AutoPreviewExtender.create(mPreviewConfigBuilder);
+ break;
+ }
+
+ assertNotNull(imageCaptureExtender);
+ assertNotNull(previewExtender);
+
+ assertTrue(previewExtender.isExtensionAvailable());
+ previewExtender.enableExtension();
+ assertTrue(imageCaptureExtender.isExtensionAvailable());
+ imageCaptureExtender.enableExtension();
+ }
+}
diff --git a/camera/integration-tests/extensionstestapp/src/main/java/androidx/camera/integration/extensions/CameraExtensionsActivity.java b/camera/integration-tests/extensionstestapp/src/main/java/androidx/camera/integration/extensions/CameraExtensionsActivity.java
index 31106c0..8a5ec06 100644
--- a/camera/integration-tests/extensionstestapp/src/main/java/androidx/camera/integration/extensions/CameraExtensionsActivity.java
+++ b/camera/integration-tests/extensionstestapp/src/main/java/androidx/camera/integration/extensions/CameraExtensionsActivity.java
@@ -310,7 +310,7 @@
+ ".jpg"),
new ImageCapture.OnImageSavedListener() {
@Override
- public void onImageSaved(File file) {
+ public void onImageSaved(@NonNull File file) {
Log.d(TAG, "Saved image to " + file);
if (!mTakePictureIdlingResource.isIdleNow()) {
@@ -330,8 +330,8 @@
@Override
public void onError(
- ImageCapture.UseCaseError useCaseError,
- String message,
+ @NonNull ImageCapture.ImageCaptureError error,
+ @NonNull String message,
Throwable cause) {
Log.e(TAG, "Failed to save image - " + message, cause);
}
@@ -514,7 +514,7 @@
@Override
public void onRequestPermissionsResult(
- int requestCode, String[] permissions, int[] grantResults) {
+ int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
switch (requestCode) {
case PERMISSIONS_REQUEST_CODE: {
// If request is cancelled, the result arrays are empty.
diff --git a/camera/integration-tests/timingtestapp/build.gradle b/camera/integration-tests/timingtestapp/build.gradle
index d88e11f..86a50ac 100644
--- a/camera/integration-tests/timingtestapp/build.gradle
+++ b/camera/integration-tests/timingtestapp/build.gradle
@@ -59,7 +59,7 @@
implementation(project(":fragment:fragment"))
implementation(project(":appcompat"))
implementation(ANDROIDX_COLLECTION)
- implementation(project(":preference")) // Still in alpha
+ implementation(project(":preference:preference")) // Still in alpha
implementation("androidx.exifinterface:exifinterface:1.0.0")
implementation(CONSTRAINT_LAYOUT, { transitive = true })
implementation(KOTLIN_STDLIB)
diff --git a/camera/integration-tests/timingtestapp/src/main/java/androidx/camera/integration/antelope/ImageUtils.kt b/camera/integration-tests/timingtestapp/src/main/java/androidx/camera/integration/antelope/ImageUtils.kt
index 0f8d309..903dd99 100644
--- a/camera/integration-tests/timingtestapp/src/main/java/androidx/camera/integration/antelope/ImageUtils.kt
+++ b/camera/integration-tests/timingtestapp/src/main/java/androidx/camera/integration/antelope/ImageUtils.kt
@@ -325,8 +325,8 @@
/** Camera X was unable to capture a still image and threw an error */
override fun onError(
- useCaseError: ImageCapture.UseCaseError?,
- message: String?,
+ imageCaptureError: ImageCapture.ImageCaptureError,
+ message: String,
cause: Throwable?
) {
logd("CameraX ImageCallback onError. Error: " + message)
diff --git a/camera/integration-tests/viewtestapp/src/main/java/androidx/camera/integration/view/CaptureViewOnTouchListener.java b/camera/integration-tests/viewtestapp/src/main/java/androidx/camera/integration/view/CaptureViewOnTouchListener.java
index e28e8e6..043604b 100644
--- a/camera/integration-tests/viewtestapp/src/main/java/androidx/camera/integration/view/CaptureViewOnTouchListener.java
+++ b/camera/integration-tests/viewtestapp/src/main/java/androidx/camera/integration/view/CaptureViewOnTouchListener.java
@@ -30,9 +30,10 @@
import android.view.ViewConfiguration;
import android.widget.Toast;
+import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
+import androidx.camera.core.ImageCapture.ImageCaptureError;
import androidx.camera.core.ImageCapture.OnImageSavedListener;
-import androidx.camera.core.ImageCapture.UseCaseError;
import androidx.camera.core.VideoCapture;
import androidx.camera.core.VideoCapture.OnVideoSavedListener;
import androidx.camera.view.CameraView;
@@ -182,7 +183,7 @@
}
@Override
- public void onImageSaved(File file) {
+ public void onImageSaved(@NonNull File file) {
report("Picture saved to " + file.getAbsolutePath());
// Print out metadata about the picture
@@ -192,20 +193,21 @@
}
@Override
- public void onVideoSaved(File file) {
+ public void onVideoSaved(@NonNull File file) {
report("Video saved to " + file.getAbsolutePath());
broadcastVideo(file);
}
@Override
- public void onError(UseCaseError useCaseError, String message, @Nullable Throwable cause) {
+ public void onError(@NonNull ImageCaptureError imageCaptureError, @NonNull String message,
+ @Nullable Throwable cause) {
report("Failure");
}
@Override
public void onError(
- VideoCapture.UseCaseError useCaseError,
- String message,
+ @NonNull VideoCapture.VideoCaptureError videoCaptureError,
+ @NonNull String message,
@Nullable Throwable cause) {
report("Failure");
}
diff --git a/car/core/api/1.0.0-alpha8.txt b/car/core/api/1.0.0-alpha8.txt
index bf6878c..79cec7e 100644
--- a/car/core/api/1.0.0-alpha8.txt
+++ b/car/core/api/1.0.0-alpha8.txt
@@ -288,13 +288,15 @@
}
public static final class CarMenuItem.Builder {
- ctor public CarMenuItem.Builder();
+ ctor @Deprecated public CarMenuItem.Builder();
+ ctor public CarMenuItem.Builder(android.content.Context);
method public androidx.car.widget.CarMenuItem build();
method public androidx.car.widget.CarMenuItem.Builder setCheckable(boolean);
method public androidx.car.widget.CarMenuItem.Builder setChecked(boolean);
method public androidx.car.widget.CarMenuItem.Builder setDisplayBehavior(androidx.car.widget.CarMenuItem.DisplayBehavior);
method public androidx.car.widget.CarMenuItem.Builder setEnabled(boolean);
method public androidx.car.widget.CarMenuItem.Builder setIcon(android.graphics.drawable.Drawable);
+ method public androidx.car.widget.CarMenuItem.Builder setIcon(@DrawableRes int);
method @Deprecated public androidx.car.widget.CarMenuItem.Builder setIcon(android.content.Context, @DrawableRes int);
method public androidx.car.widget.CarMenuItem.Builder setOnClickListener(androidx.car.widget.CarMenuItem.OnClickListener);
method public androidx.car.widget.CarMenuItem.Builder setStyle(@StyleRes int);
@@ -497,6 +499,7 @@
method public void removeOnScrollListener(androidx.recyclerview.widget.RecyclerView.OnScrollListener);
method public void scrollToPosition(int);
method public void setAdapter(androidx.recyclerview.widget.RecyclerView.Adapter<? extends androidx.recyclerview.widget.RecyclerView.ViewHolder>);
+ method public void setAlphaJumpAdapter(androidx.car.widget.AlphaJumpAdapter);
method public void setAlphaJumpVisible(boolean);
method public void setDividerColor(@ColorInt int);
method public void setDividerVisibilityManager(androidx.car.widget.PagedListView.DividerVisibilityManager!);
@@ -571,7 +574,8 @@
method public void setButtonTintColor(@ColorRes int);
method public void setDownButtonIcon(android.graphics.drawable.Drawable!);
method public void setDownEnabled(boolean);
- method public void setPaginationListener(androidx.car.widget.PagedScrollBarView.PaginationListener!);
+ method public void setOnAlphaJumpListener(androidx.car.widget.PagedScrollBarView.OnAlphaJumpListener?);
+ method public void setPaginationListener(androidx.car.widget.PagedScrollBarView.PaginationListener?);
method public void setParameters(@IntRange(from=0) int, @IntRange(from=0) int, @IntRange(from=0) int, boolean);
method public void setScrollbarThumbColor(@ColorRes int);
method public void setScrollbarThumbEnabled(boolean);
@@ -579,8 +583,12 @@
method public void setUpEnabled(boolean);
}
- public static interface PagedScrollBarView.PaginationListener {
+ public static interface PagedScrollBarView.OnAlphaJumpListener {
method public void onAlphaJump();
+ }
+
+ public static interface PagedScrollBarView.PaginationListener {
+ method @Deprecated public default void onAlphaJump();
method public void onPaginate(int);
field public static final int PAGE_DOWN = 1; // 0x1
field public static final int PAGE_UP = 0; // 0x0
diff --git a/car/core/api/current.txt b/car/core/api/current.txt
index bf6878c..79cec7e 100644
--- a/car/core/api/current.txt
+++ b/car/core/api/current.txt
@@ -288,13 +288,15 @@
}
public static final class CarMenuItem.Builder {
- ctor public CarMenuItem.Builder();
+ ctor @Deprecated public CarMenuItem.Builder();
+ ctor public CarMenuItem.Builder(android.content.Context);
method public androidx.car.widget.CarMenuItem build();
method public androidx.car.widget.CarMenuItem.Builder setCheckable(boolean);
method public androidx.car.widget.CarMenuItem.Builder setChecked(boolean);
method public androidx.car.widget.CarMenuItem.Builder setDisplayBehavior(androidx.car.widget.CarMenuItem.DisplayBehavior);
method public androidx.car.widget.CarMenuItem.Builder setEnabled(boolean);
method public androidx.car.widget.CarMenuItem.Builder setIcon(android.graphics.drawable.Drawable);
+ method public androidx.car.widget.CarMenuItem.Builder setIcon(@DrawableRes int);
method @Deprecated public androidx.car.widget.CarMenuItem.Builder setIcon(android.content.Context, @DrawableRes int);
method public androidx.car.widget.CarMenuItem.Builder setOnClickListener(androidx.car.widget.CarMenuItem.OnClickListener);
method public androidx.car.widget.CarMenuItem.Builder setStyle(@StyleRes int);
@@ -497,6 +499,7 @@
method public void removeOnScrollListener(androidx.recyclerview.widget.RecyclerView.OnScrollListener);
method public void scrollToPosition(int);
method public void setAdapter(androidx.recyclerview.widget.RecyclerView.Adapter<? extends androidx.recyclerview.widget.RecyclerView.ViewHolder>);
+ method public void setAlphaJumpAdapter(androidx.car.widget.AlphaJumpAdapter);
method public void setAlphaJumpVisible(boolean);
method public void setDividerColor(@ColorInt int);
method public void setDividerVisibilityManager(androidx.car.widget.PagedListView.DividerVisibilityManager!);
@@ -571,7 +574,8 @@
method public void setButtonTintColor(@ColorRes int);
method public void setDownButtonIcon(android.graphics.drawable.Drawable!);
method public void setDownEnabled(boolean);
- method public void setPaginationListener(androidx.car.widget.PagedScrollBarView.PaginationListener!);
+ method public void setOnAlphaJumpListener(androidx.car.widget.PagedScrollBarView.OnAlphaJumpListener?);
+ method public void setPaginationListener(androidx.car.widget.PagedScrollBarView.PaginationListener?);
method public void setParameters(@IntRange(from=0) int, @IntRange(from=0) int, @IntRange(from=0) int, boolean);
method public void setScrollbarThumbColor(@ColorRes int);
method public void setScrollbarThumbEnabled(boolean);
@@ -579,8 +583,12 @@
method public void setUpEnabled(boolean);
}
- public static interface PagedScrollBarView.PaginationListener {
+ public static interface PagedScrollBarView.OnAlphaJumpListener {
method public void onAlphaJump();
+ }
+
+ public static interface PagedScrollBarView.PaginationListener {
+ method @Deprecated public default void onAlphaJump();
method public void onPaginate(int);
field public static final int PAGE_DOWN = 1; // 0x1
field public static final int PAGE_UP = 0; // 0x0
diff --git a/car/core/api/res-1.0.0-alpha8.txt b/car/core/api/res-1.0.0-alpha8.txt
index 3212b19..f50f477 100644
--- a/car/core/api/res-1.0.0-alpha8.txt
+++ b/car/core/api/res-1.0.0-alpha8.txt
@@ -17,41 +17,56 @@
style TextAppearance_Car_Body1
style TextAppearance_Car_Body1_Light
style TextAppearance_Car_Body1_Medium
+style TextAppearance_Car_Body1_Medium_Dark
style TextAppearance_Car_Body1_Medium_Light
style TextAppearance_Car_Body2
+style TextAppearance_Car_Body2_Dark
style TextAppearance_Car_Body2_Light
style TextAppearance_Car_Body3
+style TextAppearance_Car_Body3_Dark
style TextAppearance_Car_Body3_Light
style TextAppearance_Car_Body3_Medium
+style TextAppearance_Car_Body3_Medium_Dark
style TextAppearance_Car_Body3_Medium_Light
style TextAppearance_Car_Body4
+style TextAppearance_Car_Body4_Dark
style TextAppearance_Car_Body4_Light
style TextAppearance_Car_Body4_Medium
+style TextAppearance_Car_Body4_Medium_Dark
style TextAppearance_Car_Body4_Medium_Light
style TextAppearance_Car_Display1
+style TextAppearance_Car_Display1_Dark
style TextAppearance_Car_Display1_Light
style TextAppearance_Car_Display2
+style TextAppearance_Car_Display2_Dark
style TextAppearance_Car_Display2_Light
style TextAppearance_Car_Display3
+style TextAppearance_Car_Display3_Dark
style TextAppearance_Car_Display3_Light
style TextAppearance_Car_Error
style TextAppearance_Car_Hint
style TextAppearance_Car_Hint_Light
style TextAppearance_Car_HintActionBar_Menu
style TextAppearance_Car_SubText1
+style TextAppearance_Car_SubText1_Dark
style TextAppearance_Car_SubText1_Light
style TextAppearance_Car_SubText1_Medium
style TextAppearance_Car_SubText1_Medium_Accent
+style TextAppearance_Car_SubText1_Medium_Dark
style TextAppearance_Car_SubText1_Medium_Light
style TextAppearance_Car_SubText2
+style TextAppearance_Car_SubText2_Dark
style TextAppearance_Car_SubText2_Light
style TextAppearance_Car_SubText2_Medium
style TextAppearance_Car_SubText2_Medium_Accent
+style TextAppearance_Car_SubText2_Medium_Dark
style TextAppearance_Car_SubText2_Medium_Light
style TextAppearance_Car_SubText3
+style TextAppearance_Car_SubText3_Dark
style TextAppearance_Car_SubText3_Light
style TextAppearance_Car_SubText3_Medium
style TextAppearance_Car_SubText3_Medium_Accent
+style TextAppearance_Car_SubText3_Medium_Dark
style TextAppearance_Car_SubText3_Medium_Light
style TextAppearance_Car_SubText4
style TextAppearance_Car_Subheader
@@ -151,12 +166,6 @@
dimen car_button_height
dimen car_button_min_width
dimen car_button_radius
-color car_car_primary_text
-color car_car_primary_text_dark
-color car_car_primary_text_light
-color car_car_secondary_text
-color car_car_secondary_text_dark
-color car_car_secondary_text_light
color car_card
color car_card_action_bar
color car_card_action_bar_dark
@@ -208,6 +217,9 @@
dimen car_padding_6
dimen car_pill_button_size
dimen car_primary_icon_size
+color car_primary_text
+color car_primary_text_dark
+color car_primary_text_light
dimen car_radius_1
dimen car_radius_2
dimen car_radius_3
@@ -216,6 +228,9 @@
color car_scrollbar_thumb_dark
color car_scrollbar_thumb_light
dimen car_secondary_icon_size
+color car_secondary_text
+color car_secondary_text_dark
+color car_secondary_text_light
color car_seekbar_track_background
color car_seekbar_track_background_dark
color car_seekbar_track_background_light
diff --git a/car/core/api/restricted_1.0.0-alpha8.txt b/car/core/api/restricted_1.0.0-alpha8.txt
index b4d0539..480b3d0 100644
--- a/car/core/api/restricted_1.0.0-alpha8.txt
+++ b/car/core/api/restricted_1.0.0-alpha8.txt
@@ -311,13 +311,15 @@
}
public static final class CarMenuItem.Builder {
- ctor public CarMenuItem.Builder();
+ ctor @Deprecated public CarMenuItem.Builder();
+ ctor public CarMenuItem.Builder(android.content.Context);
method public androidx.car.widget.CarMenuItem build();
method public androidx.car.widget.CarMenuItem.Builder setCheckable(boolean);
method public androidx.car.widget.CarMenuItem.Builder setChecked(boolean);
method public androidx.car.widget.CarMenuItem.Builder setDisplayBehavior(androidx.car.widget.CarMenuItem.DisplayBehavior);
method public androidx.car.widget.CarMenuItem.Builder setEnabled(boolean);
method public androidx.car.widget.CarMenuItem.Builder setIcon(android.graphics.drawable.Drawable);
+ method public androidx.car.widget.CarMenuItem.Builder setIcon(@DrawableRes int);
method @Deprecated public androidx.car.widget.CarMenuItem.Builder setIcon(android.content.Context, @DrawableRes int);
method public androidx.car.widget.CarMenuItem.Builder setOnClickListener(androidx.car.widget.CarMenuItem.OnClickListener);
method public androidx.car.widget.CarMenuItem.Builder setStyle(@StyleRes int);
@@ -522,6 +524,7 @@
method public void removeOnScrollListener(androidx.recyclerview.widget.RecyclerView.OnScrollListener);
method public void scrollToPosition(int);
method public void setAdapter(androidx.recyclerview.widget.RecyclerView.Adapter<? extends androidx.recyclerview.widget.RecyclerView.ViewHolder>);
+ method public void setAlphaJumpAdapter(androidx.car.widget.AlphaJumpAdapter);
method public void setAlphaJumpVisible(boolean);
method public void setDividerColor(@ColorInt int);
method public void setDividerVisibilityManager(androidx.car.widget.PagedListView.DividerVisibilityManager!);
@@ -596,7 +599,8 @@
method public void setButtonTintColor(@ColorRes int);
method public void setDownButtonIcon(android.graphics.drawable.Drawable!);
method public void setDownEnabled(boolean);
- method public void setPaginationListener(androidx.car.widget.PagedScrollBarView.PaginationListener!);
+ method public void setOnAlphaJumpListener(androidx.car.widget.PagedScrollBarView.OnAlphaJumpListener?);
+ method public void setPaginationListener(androidx.car.widget.PagedScrollBarView.PaginationListener?);
method public void setParameters(@IntRange(from=0) int, @IntRange(from=0) int, @IntRange(from=0) int, boolean);
method public void setScrollbarThumbColor(@ColorRes int);
method public void setScrollbarThumbEnabled(boolean);
@@ -604,8 +608,12 @@
method public void setUpEnabled(boolean);
}
- public static interface PagedScrollBarView.PaginationListener {
+ public static interface PagedScrollBarView.OnAlphaJumpListener {
method public void onAlphaJump();
+ }
+
+ public static interface PagedScrollBarView.PaginationListener {
+ method @Deprecated public default void onAlphaJump();
method public void onPaginate(int);
field public static final int PAGE_DOWN = 1; // 0x1
field public static final int PAGE_UP = 0; // 0x0
diff --git a/car/core/api/restricted_current.txt b/car/core/api/restricted_current.txt
index b4d0539..480b3d0 100644
--- a/car/core/api/restricted_current.txt
+++ b/car/core/api/restricted_current.txt
@@ -311,13 +311,15 @@
}
public static final class CarMenuItem.Builder {
- ctor public CarMenuItem.Builder();
+ ctor @Deprecated public CarMenuItem.Builder();
+ ctor public CarMenuItem.Builder(android.content.Context);
method public androidx.car.widget.CarMenuItem build();
method public androidx.car.widget.CarMenuItem.Builder setCheckable(boolean);
method public androidx.car.widget.CarMenuItem.Builder setChecked(boolean);
method public androidx.car.widget.CarMenuItem.Builder setDisplayBehavior(androidx.car.widget.CarMenuItem.DisplayBehavior);
method public androidx.car.widget.CarMenuItem.Builder setEnabled(boolean);
method public androidx.car.widget.CarMenuItem.Builder setIcon(android.graphics.drawable.Drawable);
+ method public androidx.car.widget.CarMenuItem.Builder setIcon(@DrawableRes int);
method @Deprecated public androidx.car.widget.CarMenuItem.Builder setIcon(android.content.Context, @DrawableRes int);
method public androidx.car.widget.CarMenuItem.Builder setOnClickListener(androidx.car.widget.CarMenuItem.OnClickListener);
method public androidx.car.widget.CarMenuItem.Builder setStyle(@StyleRes int);
@@ -522,6 +524,7 @@
method public void removeOnScrollListener(androidx.recyclerview.widget.RecyclerView.OnScrollListener);
method public void scrollToPosition(int);
method public void setAdapter(androidx.recyclerview.widget.RecyclerView.Adapter<? extends androidx.recyclerview.widget.RecyclerView.ViewHolder>);
+ method public void setAlphaJumpAdapter(androidx.car.widget.AlphaJumpAdapter);
method public void setAlphaJumpVisible(boolean);
method public void setDividerColor(@ColorInt int);
method public void setDividerVisibilityManager(androidx.car.widget.PagedListView.DividerVisibilityManager!);
@@ -596,7 +599,8 @@
method public void setButtonTintColor(@ColorRes int);
method public void setDownButtonIcon(android.graphics.drawable.Drawable!);
method public void setDownEnabled(boolean);
- method public void setPaginationListener(androidx.car.widget.PagedScrollBarView.PaginationListener!);
+ method public void setOnAlphaJumpListener(androidx.car.widget.PagedScrollBarView.OnAlphaJumpListener?);
+ method public void setPaginationListener(androidx.car.widget.PagedScrollBarView.PaginationListener?);
method public void setParameters(@IntRange(from=0) int, @IntRange(from=0) int, @IntRange(from=0) int, boolean);
method public void setScrollbarThumbColor(@ColorRes int);
method public void setScrollbarThumbEnabled(boolean);
@@ -604,8 +608,12 @@
method public void setUpEnabled(boolean);
}
- public static interface PagedScrollBarView.PaginationListener {
+ public static interface PagedScrollBarView.OnAlphaJumpListener {
method public void onAlphaJump();
+ }
+
+ public static interface PagedScrollBarView.PaginationListener {
+ method @Deprecated public default void onAlphaJump();
method public void onPaginate(int);
field public static final int PAGE_DOWN = 1; // 0x1
field public static final int PAGE_UP = 0; // 0x0
diff --git a/car/core/res-public/values/public_colors.xml b/car/core/res-public/values/public_colors.xml
index 84b5c4c..e0b4c71 100644
--- a/car/core/res-public/values/public_colors.xml
+++ b/car/core/res-public/values/public_colors.xml
@@ -18,12 +18,12 @@
<!-- Definitions of colors to be exposed as public. -->
<resources>
<!-- Car text colors -->
- <public type="color" name="car_car_primary_text" />
- <public type="color" name="car_car_primary_text_dark" />
- <public type="color" name="car_car_primary_text_light" />
- <public type="color" name="car_car_secondary_text" />
- <public type="color" name="car_car_secondary_text_dark" />
- <public type="color" name="car_car_secondary_text_light" />
+ <public type="color" name="car_primary_text" />
+ <public type="color" name="car_primary_text_dark" />
+ <public type="color" name="car_primary_text_light" />
+ <public type="color" name="car_secondary_text" />
+ <public type="color" name="car_secondary_text_dark" />
+ <public type="color" name="car_secondary_text_light" />
<!-- Deprecated. Mapping to primary and secondary text colors until removal. -->
<public type="color" name="car_display3_light" />
diff --git a/car/core/res-public/values/public_styles.xml b/car/core/res-public/values/public_styles.xml
index fc85a8c..bac8212 100644
--- a/car/core/res-public/values/public_styles.xml
+++ b/car/core/res-public/values/public_styles.xml
@@ -21,38 +21,53 @@
<public type="style" name="TextAppearance.Car" />
<public type="style" name="TextAppearance.Car.Display1" />
<public type="style" name="TextAppearance.Car.Display1.Light" />
+ <public type="style" name="TextAppearance.Car.Display1.Dark" />
<public type="style" name="TextAppearance.Car.Display2" />
<public type="style" name="TextAppearance.Car.Display2.Light" />
+ <public type="style" name="TextAppearance.Car.Display2.Dark" />
<public type="style" name="TextAppearance.Car.Display3" />
<public type="style" name="TextAppearance.Car.Display3.Light" />
+ <public type="style" name="TextAppearance.Car.Display3.Dark" />
<public type="style" name="TextAppearance.Car.Body1" />
<public type="style" name="TextAppearance.Car.Body1.Medium" />
<public type="style" name="TextAppearance.Car.Body1.Light" />
<public type="style" name="TextAppearance.Car.Body1.Medium.Light" />
+ <public type="style" name="TextAppearance.Car.Body1.Medium.Dark" />
<public type="style" name="TextAppearance.Car.Body2" />
<public type="style" name="TextAppearance.Car.Body2.Light" />
+ <public type="style" name="TextAppearance.Car.Body2.Dark" />
<public type="style" name="TextAppearance.Car.Body3" />
<public type="style" name="TextAppearance.Car.Body3.Medium" />
<public type="style" name="TextAppearance.Car.Body3.Light" />
<public type="style" name="TextAppearance.Car.Body3.Medium.Light" />
+ <public type="style" name="TextAppearance.Car.Body3.Dark" />
+ <public type="style" name="TextAppearance.Car.Body3.Medium.Dark" />
<public type="style" name="TextAppearance.Car.Body4" />
<public type="style" name="TextAppearance.Car.Body4.Medium" />
<public type="style" name="TextAppearance.Car.Body4.Light" />
<public type="style" name="TextAppearance.Car.Body4.Medium.Light" />
+ <public type="style" name="TextAppearance.Car.Body4.Dark" />
+ <public type="style" name="TextAppearance.Car.Body4.Medium.Dark" />
<public type="style" name="TextAppearance.Car.SubText1" />
<public type="style" name="TextAppearance.Car.SubText1.Medium" />
<public type="style" name="TextAppearance.Car.SubText1.Light" />
<public type="style" name="TextAppearance.Car.SubText1.Medium.Light" />
+ <public type="style" name="TextAppearance.Car.SubText1.Dark" />
+ <public type="style" name="TextAppearance.Car.SubText1.Medium.Dark" />
<public type="style" name="TextAppearance.Car.SubText1.Medium.Accent" />
<public type="style" name="TextAppearance.Car.SubText2" />
<public type="style" name="TextAppearance.Car.SubText2.Medium" />
<public type="style" name="TextAppearance.Car.SubText2.Light" />
<public type="style" name="TextAppearance.Car.SubText2.Medium.Light" />
+ <public type="style" name="TextAppearance.Car.SubText2.Dark" />
+ <public type="style" name="TextAppearance.Car.SubText2.Medium.Dark" />
<public type="style" name="TextAppearance.Car.SubText2.Medium.Accent" />
<public type="style" name="TextAppearance.Car.SubText3" />
<public type="style" name="TextAppearance.Car.SubText3.Medium" />
<public type="style" name="TextAppearance.Car.SubText3.Light" />
<public type="style" name="TextAppearance.Car.SubText3.Medium.Light" />
+ <public type="style" name="TextAppearance.Car.SubText3.Dark" />
+ <public type="style" name="TextAppearance.Car.SubText3.Medium.Dark" />
<public type="style" name="TextAppearance.Car.SubText3.Medium.Accent" />
<public type="style" name="TextAppearance.Car.SubText4" />
<public type="style" name="TextAppearance.Car.Subheader" />
diff --git a/car/core/res/values-night/colors.xml b/car/core/res/values-night/colors.xml
index ceae897..8b817ff 100644
--- a/car/core/res/values-night/colors.xml
+++ b/car/core/res/values-night/colors.xml
@@ -15,13 +15,6 @@
~ limitations under the License.
-->
<resources>
- <color name="car_primary_text">@color/car_primary_text_dark</color>
- <color name="car_secondary_text">@color/car_secondary_text_dark</color>
-
- <!-- Deprecated. Mapping to primary and secondary text colors until removal. -->
- <color name="car_body1">@color/car_primary_text_dark</color>
- <color name="car_body2">@color/car_primary_text_dark</color>
-
<color name="car_tint">@color/car_tint_light</color>
<color name="car_card_dark">@color/car_grey_958</color>
diff --git a/car/core/res/values/colors.xml b/car/core/res/values/colors.xml
index 65d6cb5..2efcad2 100644
--- a/car/core/res/values/colors.xml
+++ b/car/core/res/values/colors.xml
@@ -64,21 +64,21 @@
<color name="car_indigo_800">#ff283593</color>
<!-- Various colors for text. "Light" and "dark" here refer to the lighter or darker
- shades. -->
+ shades. The default for primary and secondary text color is the lighter shade.-->
<color name="car_primary_text">@color/car_primary_text_light</color>
<color name="car_secondary_text">@color/car_secondary_text_light</color>
<!-- Deprecated. Mapping to primary and secondary text colors until removal. -->
<color name="car_display3_light">@color/car_primary_text_light</color>
- <color name="car_body1">@color/car_primary_text_light</color>
+ <color name="car_body1">@color/car_primary_text</color>
<color name="car_body1_dark">@color/car_primary_text_dark</color>
- <color name="car_body1_light">@color/car_primary_text_dark</color>
- <color name="car_body2">@color/car_primary_text_light</color>
- <color name="car_body2_light">@color/car_primary_text_dark</color>
+ <color name="car_body1_light">@color/car_primary_text_light</color>
+ <color name="car_body2">@color/car_primary_text</color>
+ <color name="car_body2_light">@color/car_primary_text_light</color>
<color name="car_body3_light">@color/car_secondary_text_light</color>
<color name="car_body3_dark">@color/car_secondary_text_dark</color>
- <color name="car_body4_light">@color/car_secondary_text_dark</color>
- <color name="car_subtext1_light">@color/car_secondary_text_dark</color>
+ <color name="car_body4_light">@color/car_secondary_text_light</color>
+ <color name="car_subtext1_light">@color/car_secondary_text_light</color>
<!-- The tinting color for an icon. This icon is assumed to be on a light background. -->
<color name="car_tint">@color/car_tint_dark</color>
diff --git a/car/core/res/values/styles.xml b/car/core/res/values/styles.xml
index ea9f5de..53efee0 100644
--- a/car/core/res/values/styles.xml
+++ b/car/core/res/values/styles.xml
@@ -32,6 +32,10 @@
<item name="android:textColor">@color/car_primary_text_light</item>
</style>
+ <style name="TextAppearance.Car.Display1.Dark">
+ <item name="android:textColor">@color/car_primary_text_dark</item>
+ </style>
+
<style name="TextAppearance.Car.Display2">
<item name="android:textStyle">normal</item>
<item name="android:textSize">@dimen/car_display2_size</item>
@@ -42,6 +46,10 @@
<item name="android:textColor">@color/car_primary_text_light</item>
</style>
+ <style name="TextAppearance.Car.Display2.Dark">
+ <item name="android:textColor">@color/car_primary_text_dark</item>
+ </style>
+
<style name="TextAppearance.Car.Display3">
<item name="android:textStyle">normal</item>
<item name="android:textSize">@dimen/car_display3_size</item>
@@ -52,6 +60,10 @@
<item name="android:textColor">@color/car_primary_text_light</item>
</style>
+ <style name="TextAppearance.Car.Display3.Dark">
+ <item name="android:textColor">@color/car_primary_text_dark</item>
+ </style>
+
<!-- The styling for body text. The color of this text changes based on the day/night mode. -->
<style name="TextAppearance.Car.Body1">
<item name="android:textStyle">normal</item>
@@ -72,6 +84,15 @@
<item name="android:textColor">@color/car_primary_text_light</item>
</style>
+ <!-- Body1 text that is permanently a dark color. -->
+ <style name="TextAppearance.Car.Body1.Dark">
+ <item name="android:textColor">@color/car_primary_text_dark</item>
+ </style>
+
+ <style name="TextAppearance.Car.Body1.Medium.Dark">
+ <item name="android:textColor">@color/car_primary_text_dark</item>
+ </style>
+
<!-- An alternate styling for body text that is a different size than CarBody1. -->
<style name="TextAppearance.Car.Body2">
<item name="android:textStyle">normal</item>
@@ -84,6 +105,11 @@
<item name="android:textColor">@color/car_primary_text_light</item>
</style>
+ <!-- Body2 text that is permanently a light color. -->
+ <style name="TextAppearance.Car.Body2.Dark">
+ <item name="android:textColor">@color/car_primary_text_dark</item>
+ </style>
+
<!-- A smaller styling for body text. The color of this text changes based on the day/night
mode. -->
<style name="TextAppearance.Car.Body3">
@@ -104,6 +130,14 @@
<item name="android:textColor">@color/car_secondary_text_light</item>
</style>
+ <style name="TextAppearance.Car.Body3.Dark">
+ <item name="android:textColor">@color/car_secondary_text_dark</item>
+ </style>
+
+ <style name="TextAppearance.Car.Body3.Medium.Dark">
+ <item name="android:textColor">@color/car_secondary_text_dark</item>
+ </style>
+
<style name="TextAppearance.Car.Body3.Medium.Accent">
<item name="android:textColor">@color/car_accent</item>
</style>
@@ -144,6 +178,14 @@
<item name="android:textColor">@color/car_secondary_text_light</item>
</style>
+ <style name="TextAppearance.Car.SubText1.Dark">
+ <item name="android:textColor">@color/car_secondary_text_dark</item>
+ </style>
+
+ <style name="TextAppearance.Car.SubText1.Medium.Dark">
+ <item name="android:textColor">@color/car_secondary_text_dark</item>
+ </style>
+
<style name="TextAppearance.Car.SubText1.Medium.Accent">
<item name="android:textColor">@color/car_accent</item>
</style>
@@ -166,6 +208,14 @@
<item name="android:textColor">@color/car_secondary_text_light</item>
</style>
+ <style name="TextAppearance.Car.SubText2.Dark">
+ <item name="android:textColor">@color/car_secondary_text_dark</item>
+ </style>
+
+ <style name="TextAppearance.Car.SubText2.Medium.Dark">
+ <item name="android:textColor">@color/car_secondary_text_dark</item>
+ </style>
+
<style name="TextAppearance.Car.SubText2.Medium.Accent">
<item name="android:textColor">@color/car_accent</item>
</style>
@@ -188,6 +238,15 @@
<item name="android:textColor">@color/car_secondary_text_light</item>
</style>
+ <style name="TextAppearance.Car.SubText3.Dark">
+ <item name="android:textColor">@color/car_secondary_text_dark</item>
+ </style>
+
+ <style name="TextAppearance.Car.SubText3.Medium.Dark">
+ <item name="android:textColor">@color/car_secondary_text_dark</item>
+ </style>
+
+
<style name="TextAppearance.Car.SubText3.Medium.Accent">
<item name="android:textColor">@color/car_accent</item>
</style>
@@ -213,6 +272,9 @@
<!-- A style for TextInputLayout hints that is fixed to a light color. -->
<style name="TextAppearance.Car.Hint.Light" parent="TextAppearance.Car.Body2.Light" />
+ <!-- A style for TextInputLayout hints that is fixed to a dark color. -->
+ <style name="TextAppearance.Car.Hint.Dark" parent="TextAppearance.Car.Body2.Dark" />
+
<!-- Styles for an error message for TextInputLayouts. -->
<style name="TextAppearance.Car.Error" parent="TextAppearance.Car.Body2">
<item name="android:textColor">@color/car_red_400</item>
@@ -227,6 +289,10 @@
<item name="android:textColor">@color/car_secondary_text_light</item>
</style>
+ <style name="TextAppearance.Car.ListItem.BodyText.Dark">
+ <item name="android:textColor">@color/car_secondary_text_dark</item>
+ </style>
+
<!-- Style for the Alpha Jump button in the Scrollbar View -->
<style name="TextAppearance.Car.AlphaJumpButton">
<item name="android:textStyle">bold</item>
@@ -380,7 +446,7 @@
<item name="android:singleLine">true</item>
<item name="android:textAllCaps">false</item>
<item name="android:background">@drawable/car_button_background</item>
- <item name="android:textColor">@android:color/black</item>
+ <item name="android:textColor">@color/car_primary_text_dark</item>
</style>
<!-- A style for buttons that has white text. -->
diff --git a/car/core/res/values/themes.xml b/car/core/res/values/themes.xml
index f2611bb..0c088e4 100644
--- a/car/core/res/values/themes.xml
+++ b/car/core/res/values/themes.xml
@@ -300,7 +300,7 @@
<item name="android:colorControlActivated">@color/car_accent_dark</item>
<item name="listItemForeground">@drawable/car_card_ripple_background</item>
<item name="android:background">@color/car_card_light</item>
- <item name="android:textColorPrimary">@android:color/black</item>
+ <item name="android:textColorPrimary">@color/car_primary_text_dark</item>
<item name="android:windowTitleStyle">@style/Widget.Car.Dialog.Title</item>
</style>
diff --git a/car/core/src/androidTest/java/androidx/car/widget/AlphaJumpPagedListViewTest.java b/car/core/src/androidTest/java/androidx/car/widget/AlphaJumpPagedListViewTest.java
index 44a55eb..2119810 100644
--- a/car/core/src/androidTest/java/androidx/car/widget/AlphaJumpPagedListViewTest.java
+++ b/car/core/src/androidTest/java/androidx/car/widget/AlphaJumpPagedListViewTest.java
@@ -87,7 +87,9 @@
try {
mActivityRule.runOnUiThread(() -> {
mPagedListView.setMaxPages(PagedListView.ItemCap.UNLIMITED);
- mPagedListView.setAdapter(new AlphaJumpTestAdapter());
+ AlphaJumpTestAdapter adapter = new AlphaJumpTestAdapter();
+ mPagedListView.setAdapter(adapter);
+ mPagedListView.setAlphaJumpAdapter(adapter);
});
} catch (Throwable throwable) {
throwable.printStackTrace();
diff --git a/car/core/src/androidTest/java/androidx/car/widget/CarToolbarTest.java b/car/core/src/androidTest/java/androidx/car/widget/CarToolbarTest.java
index 81f524c..cbc0ca2 100644
--- a/car/core/src/androidTest/java/androidx/car/widget/CarToolbarTest.java
+++ b/car/core/src/androidTest/java/androidx/car/widget/CarToolbarTest.java
@@ -298,7 +298,7 @@
public void testActionItemDisplayedOnToolbar() throws Throwable {
String actionItemText = "checkable_item_text";
CarMenuItem actionItem = new CarMenuItem
- .Builder()
+ .Builder(mActivityRule.getActivity())
.setTitle(actionItemText)
.setDisplayBehavior(CarMenuItem.DisplayBehavior.ALWAYS) // Action item
.build();
@@ -313,7 +313,7 @@
public void testSetMenuItems_NullClearsItems() throws Throwable {
String actionItemText = "checkable_item_text";
CarMenuItem actionItem = new CarMenuItem
- .Builder()
+ .Builder(mActivityRule.getActivity())
.setTitle(actionItemText)
.setDisplayBehavior(CarMenuItem.DisplayBehavior.ALWAYS) // Action item
.build();
@@ -333,10 +333,10 @@
public void testActionItemWithIconDisplaysIcon() throws Throwable {
String actionItemText = "action_item_text";
CarMenuItem actionItem = new CarMenuItem
- .Builder()
+ .Builder(mActivityRule.getActivity())
.setTitle(actionItemText)
.setDisplayBehavior(CarMenuItem.DisplayBehavior.ALWAYS) // Action item
- .setIcon(mActivity.getDrawable(android.R.drawable.sym_def_app_icon))
+ .setIcon(android.R.drawable.sym_def_app_icon)
.build();
mActivityRule.runOnUiThread(() ->
@@ -350,7 +350,7 @@
public void testSwitchActionItemDisplaysSwitchWidget() throws Throwable {
String actionItemText = "checkable_item_text";
CarMenuItem actionItem = new CarMenuItem
- .Builder()
+ .Builder(mActivityRule.getActivity())
.setTitle(actionItemText)
.setDisplayBehavior(CarMenuItem.DisplayBehavior.ALWAYS) // Action item
.setCheckable(true)
@@ -367,7 +367,7 @@
public void testSwitchActionItemSetCheckable() throws Throwable {
String actionItemText = "checkable_item_text";
CarMenuItem actionItem = new CarMenuItem
- .Builder()
+ .Builder(mActivityRule.getActivity())
.setTitle(actionItemText)
.setDisplayBehavior(CarMenuItem.DisplayBehavior.ALWAYS) // Action item
.setCheckable(true)
@@ -387,7 +387,7 @@
String actionItemText = "checkable_item_text";
CarMenuItem actionItem = new CarMenuItem
- .Builder()
+ .Builder(mActivityRule.getActivity())
.setTitle(actionItemText)
.setDisplayBehavior(CarMenuItem.DisplayBehavior.ALWAYS) // Action item
.setCheckable(true)
@@ -414,7 +414,7 @@
String actionItemText = "action_item_text";
CarMenuItem actionItem = new CarMenuItem
- .Builder()
+ .Builder(mActivityRule.getActivity())
.setDisplayBehavior(CarMenuItem.DisplayBehavior.ALWAYS) // Action item
.setTitle(actionItemText)
.setOnClickListener(item -> clicked[0] = true)
@@ -434,7 +434,7 @@
String actionItemText = "checkable_item_text";
CarMenuItem actionItem = new CarMenuItem
- .Builder()
+ .Builder(mActivityRule.getActivity())
.setDisplayBehavior(CarMenuItem.DisplayBehavior.ALWAYS) // Action item
.setTitle(actionItemText)
.setCheckable(true)
@@ -457,7 +457,7 @@
@Test
public void testOverflowButtonShownIfOverflowItems() throws Throwable {
CarMenuItem overflowItem = new CarMenuItem
- .Builder()
+ .Builder(mActivityRule.getActivity())
.setDisplayBehavior(CarMenuItem.DisplayBehavior.NEVER) // Overflow menu item
.build();
mActivityRule.runOnUiThread(() ->
@@ -469,7 +469,7 @@
@Test
public void testOverflowButtonHiddenIfNoOverflowItems() throws Throwable {
CarMenuItem actionItem = new CarMenuItem
- .Builder()
+ .Builder(mActivityRule.getActivity())
.setDisplayBehavior(CarMenuItem.DisplayBehavior.ALWAYS) // Action item
.build();
@@ -483,7 +483,7 @@
public void testOverflowMenuDisplaysNeverItem() throws Throwable {
String overflowItemText = "overflow_item_text";
CarMenuItem overflowItem = new CarMenuItem
- .Builder()
+ .Builder(mActivityRule.getActivity())
.setDisplayBehavior(CarMenuItem.DisplayBehavior.NEVER) // Overflow menu item
.setTitle(overflowItemText)
.build();
@@ -499,14 +499,14 @@
public void testOverflowMenuDoesNotDisplayActionItem() throws Throwable {
String overflowItemText = "overflow_item_text";
CarMenuItem overflowItem = new CarMenuItem
- .Builder()
+ .Builder(mActivityRule.getActivity())
.setDisplayBehavior(CarMenuItem.DisplayBehavior.NEVER) // Overflow menu item
.setTitle(overflowItemText)
.build();
String actionItemText = "action_item_text";
CarMenuItem actionItem = new CarMenuItem
- .Builder()
+ .Builder(mActivityRule.getActivity())
.setDisplayBehavior(CarMenuItem.DisplayBehavior.ALWAYS) // Action item
.setTitle(actionItemText)
.build();
@@ -521,7 +521,7 @@
@Test
public void testIsOverflowMenuShowing() throws Throwable {
CarMenuItem overflowItem = new CarMenuItem
- .Builder()
+ .Builder(mActivityRule.getActivity())
.setDisplayBehavior(CarMenuItem.DisplayBehavior.NEVER) // Overflow menu item
.build();
@@ -541,7 +541,7 @@
public void testShowOverflowMenu() throws Throwable {
String overflowItemText = "overflow_item_text";
CarMenuItem overflowItem = new CarMenuItem
- .Builder()
+ .Builder(mActivityRule.getActivity())
.setDisplayBehavior(CarMenuItem.DisplayBehavior.NEVER) // Overflow menu item
.setTitle(overflowItemText)
.build();
@@ -560,7 +560,7 @@
public void testHideOverflowMenu() throws Throwable {
String overflowItemText = "overflow_item_text";
CarMenuItem overflowItem = new CarMenuItem
- .Builder()
+ .Builder(mActivityRule.getActivity())
.setDisplayBehavior(CarMenuItem.DisplayBehavior.NEVER) // Overflow menu item
.setTitle(overflowItemText)
.build();
@@ -580,7 +580,7 @@
String overflowItemText = "overflow_item_text";
CarMenuItem overflowItem = new CarMenuItem
- .Builder()
+ .Builder(mActivityRule.getActivity())
.setDisplayBehavior(CarMenuItem.DisplayBehavior.NEVER) // Overflow menu item
.setTitle(overflowItemText)
.setOnClickListener(item -> clicked[0] = true)
@@ -601,21 +601,21 @@
List<CarMenuItem> items = new ArrayList<>();
String action1Text = "item_1";
items.add(new CarMenuItem
- .Builder()
+ .Builder(mActivityRule.getActivity())
.setTitle(action1Text)
.setDisplayBehavior(CarMenuItem.DisplayBehavior.ALWAYS)
.build());
String ifRoomItemText = "if_room";
items.add(new CarMenuItem
- .Builder()
+ .Builder(mActivityRule.getActivity())
.setTitle(ifRoomItemText)
.setDisplayBehavior(CarMenuItem.DisplayBehavior.IF_ROOM)
.build());
String action2Text = "item_2";
items.add(new CarMenuItem
- .Builder()
+ .Builder(mActivityRule.getActivity())
.setTitle(action2Text)
.setDisplayBehavior(CarMenuItem.DisplayBehavior.ALWAYS)
.build());
@@ -631,7 +631,7 @@
public void testIfRoomItemDisplayedIfRoomAvailable() throws Throwable {
String ifRoomItemText = "if_room_item_text";
CarMenuItem ifRoomItem = new CarMenuItem
- .Builder()
+ .Builder(mActivityRule.getActivity())
.setTitle(ifRoomItemText)
.setDisplayBehavior(CarMenuItem.DisplayBehavior.IF_ROOM)
.build();
@@ -647,14 +647,14 @@
List<CarMenuItem> items = new ArrayList<>();
for (int i = 0; i < CarToolbar.ACTION_ITEM_COUNT_LIMIT; i++) {
items.add(new CarMenuItem
- .Builder()
+ .Builder(mActivityRule.getActivity())
.setDisplayBehavior(CarMenuItem.DisplayBehavior.ALWAYS)
.build());
}
String ifRoomItemText = "if_room_item_text";
items.add(new CarMenuItem
- .Builder()
+ .Builder(mActivityRule.getActivity())
.setTitle(ifRoomItemText)
.setDisplayBehavior(CarMenuItem.DisplayBehavior.IF_ROOM)
.build());
@@ -675,7 +675,7 @@
String longText = mActivity.getString(R.string.over_uxr_text_length_limit);
List<CarMenuItem> items = new ArrayList<>();
items.add(new CarMenuItem
- .Builder()
+ .Builder(mActivityRule.getActivity())
.setTitle(longText)
.setDisplayBehavior(CarMenuItem.DisplayBehavior.ALWAYS)
.build());
@@ -683,7 +683,7 @@
String ifRoomItemText = "if_room_item_text";
items.add(new CarMenuItem
- .Builder()
+ .Builder(mActivityRule.getActivity())
.setTitle(ifRoomItemText)
.setDisplayBehavior(CarMenuItem.DisplayBehavior.IF_ROOM)
.build());
@@ -701,13 +701,13 @@
public void testIfRoomItemPushedFromActionToOverflowIfLimitExceeded() throws Throwable {
List<CarMenuItem> items = new ArrayList<>();
items.add(new CarMenuItem
- .Builder()
+ .Builder(mActivityRule.getActivity())
.setDisplayBehavior(CarMenuItem.DisplayBehavior.ALWAYS)
.build());
String ifRoomItemText = "if_room_item_text";
items.add(new CarMenuItem
- .Builder()
+ .Builder(mActivityRule.getActivity())
.setTitle(ifRoomItemText)
.setDisplayBehavior(CarMenuItem.DisplayBehavior.IF_ROOM)
.build());
@@ -718,7 +718,7 @@
for (int i = 0; i < CarToolbar.ACTION_ITEM_COUNT_LIMIT - 1; i++) {
items.add(new CarMenuItem
- .Builder()
+ .Builder(mActivityRule.getActivity())
.setDisplayBehavior(CarMenuItem.DisplayBehavior.ALWAYS)
.build());
}
diff --git a/car/core/src/main/java/androidx/car/app/CarDialogUtil.java b/car/core/src/main/java/androidx/car/app/CarDialogUtil.java
index f47e7f1..39f2244 100644
--- a/car/core/src/main/java/androidx/car/app/CarDialogUtil.java
+++ b/car/core/src/main/java/androidx/car/app/CarDialogUtil.java
@@ -99,10 +99,6 @@
Log.e(TAG, "Unknown pagination direction (" + direction + ")");
}
}
-
- @Override
- public void onAlphaJump() {
- }
});
}
diff --git a/car/core/src/main/java/androidx/car/widget/CarMenuItem.java b/car/core/src/main/java/androidx/car/widget/CarMenuItem.java
index 400a2e0..53f2245 100644
--- a/car/core/src/main/java/androidx/car/widget/CarMenuItem.java
+++ b/car/core/src/main/java/androidx/car/widget/CarMenuItem.java
@@ -30,19 +30,18 @@
*
* <p>The following properties can be specified:
* <ul>
- * <li>Title - Primary text that is shown on the item.
- * <li>{@link CarMenuItem.OnClickListener} - Listener that handles the clicks on the item.
- * <li>Icon - An icon shown before the title, if the item is not checkable (a switch).
- * <li>Style - A Resource Id that specifies the style of the item if it's not an overflow item.
- * <li>Enabled - A boolean that specifies whether the item is enabled or disabled.
- * <li>Checkable - A boolean that specifies whether the item is checkable (a switch) or not.
- * <li>Checked - A boolean that specifies whether the item is currently checked or not.
- * <li>DisplayBehavior - A {@link DisplayBehavior} that specifies where the item is displayed.
+ * <li>Title - Primary text that is shown on the item.
+ * <li>{@link CarMenuItem.OnClickListener} - Listener that handles the clicks on the item.
+ * <li>Icon - An icon shown before the title, if the item is not checkable (a switch).
+ * <li>Style - A Resource Id that specifies the style of the item if it's not an overflow item.
+ * <li>Enabled - A boolean that specifies whether the item is enabled or disabled.
+ * <li>Checkable - A boolean that specifies whether the item is checkable (a switch) or not.
+ * <li>Checked - A boolean that specifies whether the item is currently checked or not.
+ * <li>DisplayBehavior - A {@link DisplayBehavior} that specifies where the item is displayed.
* </ul>
*
* <p>Properties such as the title, isEnabled, and isChecked can be modified
* after creation, and as such, have setters in the class and the builder.
- *
*/
public final class CarMenuItem {
/**
@@ -76,6 +75,7 @@
*/
NEVER
}
+
@Nullable
private CharSequence mTitle;
private boolean mIsEnabled;
@@ -198,6 +198,8 @@
* Builder for creating a {@link CarMenuItem}
*/
public static final class Builder {
+ @Nullable
+ private Context mContext;
CharSequence mTitle;
@Nullable
OnClickListener mOnClickListener;
@@ -212,6 +214,25 @@
DisplayBehavior mDisplayBehavior = DisplayBehavior.IF_ROOM;
/**
+ * Creates a new instance of the {@code Builder}.
+ *
+ * @deprecated Use
+ * {@link androidx.car.widget.CarMenuItem.Builder#CarMenuItem.Builder(Context)} instead.
+ */
+ @Deprecated
+ public Builder() {
+ }
+
+ /**
+ * Creates a new instance of the {@code Builder}.
+ *
+ * @param context The {@code Context} that the menu item is to be created in.
+ */
+ public Builder(@NonNull Context context) {
+ mContext = context;
+ }
+
+ /**
* Sets the title of the {@code CarMenuItem}.
*
* @param title Title of the {@code CarMenuItem}.
@@ -259,13 +280,24 @@
return this;
}
+ @NonNull
+ public Builder setIcon(@DrawableRes int iconId) {
+ if (mContext == null) {
+ throw new IllegalStateException(
+ "Cannot use deprecated constructor to create CarMenuItem.Builder object. "
+ + "Use CarMenuItem.Builder(Context) instead");
+ }
+
+ mIconDrawable = mContext.getDrawable(iconId);
+ return this;
+ }
+
/**
* Sets the icon of the {@code CarMenuItem}.
*
- * @param context Context to load the drawable resource with.
+ * @param context Context to load the drawable resource with.
* @param iconResId Resource id of icon of the {@code CarMenuItem}.
* @return This {@code Builder} object to allow call chaining.
- *
* @deprecated Use {@link #setIcon(Drawable)} instead.
*/
@Deprecated
diff --git a/car/core/src/main/java/androidx/car/widget/PagedListView.java b/car/core/src/main/java/androidx/car/widget/PagedListView.java
index ea89551..5b9d47c 100644
--- a/car/core/src/main/java/androidx/car/widget/PagedListView.java
+++ b/car/core/src/main/java/androidx/car/widget/PagedListView.java
@@ -118,6 +118,7 @@
private int mRowsPerPage = -1;
private RecyclerView.Adapter<? extends RecyclerView.ViewHolder> mAdapter;
+ private AlphaJumpAdapter mAlphaJumpAdapter;
/** Maximum number of pages to show. */
private int mMaxPages = UNLIMITED_PAGES;
@@ -356,11 +357,6 @@
Log.e(TAG, "Unknown pagination direction (" + direction + ")");
}
}
-
- @Override
- public void onAlphaJump() {
- setAlphaJumpVisible(true);
- }
});
if (a.hasValue(R.styleable.PagedListView_scrollBarGravity)) {
@@ -781,7 +777,17 @@
mRecyclerView.setAdapter(adapter);
updateMaxItems();
- updateAlphaJump();
+ }
+
+ /**
+ * Sets the alpha jump adapter for the list.
+ *
+ * @param adapter The alpha jump adapter to set for the list.
+ */
+ public void setAlphaJumpAdapter(@NonNull AlphaJumpAdapter adapter) {
+ mAlphaJumpAdapter = adapter;
+ mScrollBarView.setOnAlphaJumpListener(() -> setAlphaJumpVisible(true));
+ mScrollBarView.setShowAlphaJump(true);
}
/**
@@ -1388,11 +1394,6 @@
dispatchThawSelfOnly(container);
}
- private void updateAlphaJump() {
- boolean supportsAlphaJump = (mAdapter instanceof AlphaJumpAdapter);
- mScrollBarView.setShowAlphaJump(supportsAlphaJump);
- }
-
/**
* Returns {@code true} if the Alpha Jump Overlay is shown.
*/
diff --git a/car/core/src/main/java/androidx/car/widget/PagedScrollBarView.java b/car/core/src/main/java/androidx/car/widget/PagedScrollBarView.java
index 78cc96b..fa47350 100644
--- a/car/core/src/main/java/androidx/car/widget/PagedScrollBarView.java
+++ b/car/core/src/main/java/androidx/car/widget/PagedScrollBarView.java
@@ -34,6 +34,7 @@
import androidx.annotation.ColorRes;
import androidx.annotation.DrawableRes;
import androidx.annotation.IntRange;
+import androidx.annotation.Nullable;
import androidx.car.R;
import androidx.core.content.ContextCompat;
@@ -58,6 +59,22 @@
* <p>AlphaJump buckets only support characters from the {@code en} language. Characters
* from other languages not supported and bucketing behavior is undefined. AlphaJump overlay
* is still displayed if all buckets are empty.
+ *
+ * @deprecated Use {@link OnAlphaJumpListener#onAlphaJump()} instead.
+ */
+ @Deprecated
+ default void onAlphaJump() {}
+ }
+
+ public interface OnAlphaJumpListener {
+ /**
+ * Called when the 'alpha jump' button is clicked and the linked view should switch into
+ * alpha jump mode, where we display a list of buttons to allow the user to quickly scroll
+ * to a certain point in the list, bypassing a lot of manual scrolling.
+ *
+ * <p>AlphaJump buckets only support characters from the {@code en} language. Characters
+ * from other languages not supported and bucketing behavior is undefined. AlphaJump overlay
+ * is still displayed if all buckets are empty.
*/
void onAlphaJump();
}
@@ -166,12 +183,21 @@
*
* @param listener The listener to set.
*/
- public void setPaginationListener(PaginationListener listener) {
+ public void setPaginationListener(@Nullable PaginationListener listener) {
mUpButtonClickListener.setPaginationListener(listener);
mDownButtonClickListener.setPaginationListener(listener);
mAlphaJumpButtonClickListener.setPaginationListener(listener);
}
+ /**
+ * Sets the listener that will be notified when alpha jump button has been pressed.
+ *
+ * @param listener The listener to set.
+ */
+ public void setOnAlphaJumpListener(@Nullable OnAlphaJumpListener listener) {
+ mAlphaJumpButtonClickListener.setOnAlphaJumpListener(listener);
+ }
+
/** Returns {@code true} if the "up" button is pressed */
public boolean isUpPressed() {
return mUpButton.isPressed();
@@ -554,17 +580,26 @@
}
private static class AlphaJumpButtonClickListener implements View.OnClickListener {
+ private OnAlphaJumpListener mOnAlphaJumpListener;
private PaginationListener mPaginationListener;
AlphaJumpButtonClickListener() {
}
+ public void setOnAlphaJumpListener(OnAlphaJumpListener listener) {
+ mOnAlphaJumpListener = listener;
+ }
+
public void setPaginationListener(PaginationListener listener) {
mPaginationListener = listener;
}
@Override
public void onClick(View v) {
+ if (mOnAlphaJumpListener != null) {
+ mOnAlphaJumpListener.onAlphaJump();
+ }
+
if (mPaginationListener != null) {
mPaginationListener.onAlphaJump();
}
diff --git a/car/core/src/main/res/color-night/car_primary_text_light.xml b/car/core/src/main/res/color-night/car_primary_text_light.xml
new file mode 100644
index 0000000..9b5d499
--- /dev/null
+++ b/car/core/src/main/res/color-night/car_primary_text_light.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2018 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.
+-->
+<!-- Default text colors for car buttons when enabled/disabled. -->
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+ <item android:state_enabled="false"
+ android:alpha="0.44"
+ android:color="@android:color/white" />
+ <item android:color="@android:color/white"
+ android:alpha="0.88" />
+</selector>
\ No newline at end of file
diff --git a/car/core/src/main/res/color-night/car_secondary_text_light.xml b/car/core/src/main/res/color-night/car_secondary_text_light.xml
new file mode 100644
index 0000000..ee6e15c
--- /dev/null
+++ b/car/core/src/main/res/color-night/car_secondary_text_light.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2018 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.
+-->
+<!-- Default text colors for car buttons when enabled/disabled. -->
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+ <item android:state_enabled="false"
+ android:alpha="0.30"
+ android:color="@android:color/white"/>
+ <item android:color="@android:color/white"
+ android:alpha="0.60" />
+</selector>
\ No newline at end of file
diff --git a/car/core/src/main/res/color/car_primary_text_dark.xml b/car/core/src/main/res/color/car_primary_text_dark.xml
index 9b5d499..2b24eaf 100644
--- a/car/core/src/main/res/color/car_primary_text_dark.xml
+++ b/car/core/src/main/res/color/car_primary_text_dark.xml
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
- Copyright (C) 2018 The Android Open Source Project
+ Copyright (C) 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.
@@ -17,8 +17,7 @@
<!-- Default text colors for car buttons when enabled/disabled. -->
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:state_enabled="false"
- android:alpha="0.44"
- android:color="@android:color/white" />
- <item android:color="@android:color/white"
- android:alpha="0.88" />
+ android:alpha="0.40"
+ android:color="@android:color/white" />
+ <item android:color="@android:color/black" />
</selector>
\ No newline at end of file
diff --git a/car/core/src/main/res/color/car_secondary_text_dark.xml b/car/core/src/main/res/color/car_secondary_text_dark.xml
index ee6e15c..2b24eaf 100644
--- a/car/core/src/main/res/color/car_secondary_text_dark.xml
+++ b/car/core/src/main/res/color/car_secondary_text_dark.xml
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
- Copyright (C) 2018 The Android Open Source Project
+ Copyright (C) 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.
@@ -17,8 +17,7 @@
<!-- Default text colors for car buttons when enabled/disabled. -->
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:state_enabled="false"
- android:alpha="0.30"
- android:color="@android:color/white"/>
- <item android:color="@android:color/white"
- android:alpha="0.60" />
+ android:alpha="0.40"
+ android:color="@android:color/white" />
+ <item android:color="@android:color/black" />
</selector>
\ No newline at end of file
diff --git a/car/moderator/build.gradle b/car/moderator/build.gradle
index 4d19dbd..bd7370d 100644
--- a/car/moderator/build.gradle
+++ b/car/moderator/build.gradle
@@ -26,7 +26,7 @@
dependencies {
api("androidx.annotation:annotation:1.1.0")
- api("androidx.core:core:1.1.0-rc01")
+ api("androidx.core:core:1.1.0")
androidTestImplementation(ANDROIDX_TEST_EXT_JUNIT)
androidTestImplementation(ANDROIDX_TEST_CORE)
diff --git a/compose/compose-runtime/src/androidTest/java/EMPTY_FILE b/compose/compose-runtime/src/androidTest/java/EMPTY_FILE
new file mode 100644
index 0000000..d2b6b6f
--- /dev/null
+++ b/compose/compose-runtime/src/androidTest/java/EMPTY_FILE
@@ -0,0 +1,4 @@
+This file is needed to work around a problem with the Kotlin-MPP plugin with
+single-Kotlin-module test structure. Without this file,
+./gradlew :compose:compose-runtime:connectedAndroidTest
+does not do anything. See b/139385662 for more details.
diff --git a/content/build.gradle b/content/build.gradle
index cafb452..4a27186 100644
--- a/content/build.gradle
+++ b/content/build.gradle
@@ -26,7 +26,7 @@
dependencies {
api("androidx.annotation:annotation:1.1.0")
- api("androidx.core:core:1.1.0-rc01")
+ api("androidx.core:core:1.1.0")
implementation("androidx.collection:collection:1.1.0")
androidTestImplementation(JUNIT)
diff --git a/coordinatorlayout/build.gradle b/coordinatorlayout/build.gradle
index ae2a733..67dfc84 100644
--- a/coordinatorlayout/build.gradle
+++ b/coordinatorlayout/build.gradle
@@ -10,8 +10,7 @@
dependencies {
api("androidx.annotation:annotation:1.1.0")
- // TODO: change to 1.1.0-alpha04 after release
- api("androidx.core:core:1.1.0-rc01")
+ api("androidx.core:core:1.1.0")
implementation("androidx.collection:collection:1.0.0")
api("androidx.customview:customview:1.0.0")
diff --git a/coordinatorlayout/src/androidTest/res/layout/design_snackbar_behavior_layout_attr.xml b/coordinatorlayout/src/androidTest/res/layout/design_snackbar_behavior_layout_attr.xml
index 5cf76a1..4fdc5d5 100644
--- a/coordinatorlayout/src/androidTest/res/layout/design_snackbar_behavior_layout_attr.xml
+++ b/coordinatorlayout/src/androidTest/res/layout/design_snackbar_behavior_layout_attr.xml
@@ -22,7 +22,7 @@
android:layout_width="match_parent"
android:layout_height="match_parent">
- <androidx.appcompat.widget.AppCompatTextView
+ <TextView
android:id="@+id/text"
android:layout_height="wrap_content"
android:layout_width="wrap_content"
@@ -31,7 +31,7 @@
android:textSize="18dip"
android:textStyle="bold"
android:padding="12dip"
- app:textAllCaps="true"
+ android:textAllCaps="true"
app:layout_behavior="androidx.coordinatorlayout.custom.TestFloatingBehavior" />
</androidx.coordinatorlayout.widget.CoordinatorLayout>
\ No newline at end of file
diff --git a/core/core/api/1.2.0-alpha04.txt b/core/core/api/1.2.0-alpha04.txt
index 9078e34..36ee996 100644
--- a/core/core/api/1.2.0-alpha04.txt
+++ b/core/core/api/1.2.0-alpha04.txt
@@ -916,6 +916,7 @@
method public android.content.Intent getIntent();
method public android.content.Intent![] getIntents();
method public CharSequence? getLongLabel();
+ method public int getRank();
method public CharSequence getShortLabel();
method @RequiresApi(25) public android.content.pm.ShortcutInfo! toShortcutInfo();
}
@@ -931,9 +932,11 @@
method public androidx.core.content.pm.ShortcutInfoCompat.Builder setIntent(android.content.Intent);
method public androidx.core.content.pm.ShortcutInfoCompat.Builder setIntents(android.content.Intent![]);
method public androidx.core.content.pm.ShortcutInfoCompat.Builder setLongLabel(CharSequence);
- method public androidx.core.content.pm.ShortcutInfoCompat.Builder setLongLived();
+ method @Deprecated public androidx.core.content.pm.ShortcutInfoCompat.Builder setLongLived();
+ method public androidx.core.content.pm.ShortcutInfoCompat.Builder setLongLived(boolean);
method public androidx.core.content.pm.ShortcutInfoCompat.Builder setPerson(androidx.core.app.Person);
method public androidx.core.content.pm.ShortcutInfoCompat.Builder setPersons(androidx.core.app.Person![]);
+ method public androidx.core.content.pm.ShortcutInfoCompat.Builder setRank(int);
method public androidx.core.content.pm.ShortcutInfoCompat.Builder setShortLabel(CharSequence);
}
diff --git a/core/core/api/current.txt b/core/core/api/current.txt
index 9078e34..36ee996 100644
--- a/core/core/api/current.txt
+++ b/core/core/api/current.txt
@@ -916,6 +916,7 @@
method public android.content.Intent getIntent();
method public android.content.Intent![] getIntents();
method public CharSequence? getLongLabel();
+ method public int getRank();
method public CharSequence getShortLabel();
method @RequiresApi(25) public android.content.pm.ShortcutInfo! toShortcutInfo();
}
@@ -931,9 +932,11 @@
method public androidx.core.content.pm.ShortcutInfoCompat.Builder setIntent(android.content.Intent);
method public androidx.core.content.pm.ShortcutInfoCompat.Builder setIntents(android.content.Intent![]);
method public androidx.core.content.pm.ShortcutInfoCompat.Builder setLongLabel(CharSequence);
- method public androidx.core.content.pm.ShortcutInfoCompat.Builder setLongLived();
+ method @Deprecated public androidx.core.content.pm.ShortcutInfoCompat.Builder setLongLived();
+ method public androidx.core.content.pm.ShortcutInfoCompat.Builder setLongLived(boolean);
method public androidx.core.content.pm.ShortcutInfoCompat.Builder setPerson(androidx.core.app.Person);
method public androidx.core.content.pm.ShortcutInfoCompat.Builder setPersons(androidx.core.app.Person![]);
+ method public androidx.core.content.pm.ShortcutInfoCompat.Builder setRank(int);
method public androidx.core.content.pm.ShortcutInfoCompat.Builder setShortLabel(CharSequence);
}
diff --git a/core/core/api/restricted_1.2.0-alpha04.txt b/core/core/api/restricted_1.2.0-alpha04.txt
index 1841c53..fd3ab83 100644
--- a/core/core/api/restricted_1.2.0-alpha04.txt
+++ b/core/core/api/restricted_1.2.0-alpha04.txt
@@ -1017,6 +1017,7 @@
method public android.content.Intent getIntent();
method public android.content.Intent![] getIntents();
method public CharSequence? getLongLabel();
+ method public int getRank();
method public CharSequence getShortLabel();
method @RequiresApi(25) public android.content.pm.ShortcutInfo! toShortcutInfo();
}
@@ -1034,9 +1035,11 @@
method public androidx.core.content.pm.ShortcutInfoCompat.Builder setIntent(android.content.Intent);
method public androidx.core.content.pm.ShortcutInfoCompat.Builder setIntents(android.content.Intent![]);
method public androidx.core.content.pm.ShortcutInfoCompat.Builder setLongLabel(CharSequence);
- method public androidx.core.content.pm.ShortcutInfoCompat.Builder setLongLived();
+ method @Deprecated public androidx.core.content.pm.ShortcutInfoCompat.Builder setLongLived();
+ method public androidx.core.content.pm.ShortcutInfoCompat.Builder setLongLived(boolean);
method public androidx.core.content.pm.ShortcutInfoCompat.Builder setPerson(androidx.core.app.Person);
method public androidx.core.content.pm.ShortcutInfoCompat.Builder setPersons(androidx.core.app.Person![]);
+ method public androidx.core.content.pm.ShortcutInfoCompat.Builder setRank(int);
method public androidx.core.content.pm.ShortcutInfoCompat.Builder setShortLabel(CharSequence);
}
diff --git a/core/core/api/restricted_current.txt b/core/core/api/restricted_current.txt
index 1841c53..fd3ab83 100644
--- a/core/core/api/restricted_current.txt
+++ b/core/core/api/restricted_current.txt
@@ -1017,6 +1017,7 @@
method public android.content.Intent getIntent();
method public android.content.Intent![] getIntents();
method public CharSequence? getLongLabel();
+ method public int getRank();
method public CharSequence getShortLabel();
method @RequiresApi(25) public android.content.pm.ShortcutInfo! toShortcutInfo();
}
@@ -1034,9 +1035,11 @@
method public androidx.core.content.pm.ShortcutInfoCompat.Builder setIntent(android.content.Intent);
method public androidx.core.content.pm.ShortcutInfoCompat.Builder setIntents(android.content.Intent![]);
method public androidx.core.content.pm.ShortcutInfoCompat.Builder setLongLabel(CharSequence);
- method public androidx.core.content.pm.ShortcutInfoCompat.Builder setLongLived();
+ method @Deprecated public androidx.core.content.pm.ShortcutInfoCompat.Builder setLongLived();
+ method public androidx.core.content.pm.ShortcutInfoCompat.Builder setLongLived(boolean);
method public androidx.core.content.pm.ShortcutInfoCompat.Builder setPerson(androidx.core.app.Person);
method public androidx.core.content.pm.ShortcutInfoCompat.Builder setPersons(androidx.core.app.Person![]);
+ method public androidx.core.content.pm.ShortcutInfoCompat.Builder setRank(int);
method public androidx.core.content.pm.ShortcutInfoCompat.Builder setShortLabel(CharSequence);
}
diff --git a/core/core/src/androidTest/java/androidx/core/content/pm/ShortcutInfoCompatTest.java b/core/core/src/androidTest/java/androidx/core/content/pm/ShortcutInfoCompatTest.java
index 516759c..6d00a7c 100644
--- a/core/core/src/androidTest/java/androidx/core/content/pm/ShortcutInfoCompatTest.java
+++ b/core/core/src/androidTest/java/androidx/core/content/pm/ShortcutInfoCompatTest.java
@@ -126,6 +126,7 @@
assertEquals(TEST_SHORTCUT_ID, compat.getId());
assertEquals(TEST_SHORTCUT_SHORT_LABEL, compat.getShortLabel());
assertEquals(mAction, compat.getIntent());
+ assertEquals(0, compat.getRank());
assertNull(compat.getLongLabel());
assertNull(compat.getDisabledMessage());
assertNull(compat.getActivity());
@@ -140,11 +141,13 @@
Set<String> categories = new HashSet<>();
categories.add("cat1");
categories.add("cat2");
+ int rank = 3;
ShortcutInfoCompat compat = mBuilder
.setActivity(activity)
.setCategories(categories)
.setDisabledMessage(disabledMessage)
.setLongLabel(longLabel)
+ .setRank(rank)
.build();
ShortcutInfoCompat copyCompat = new ShortcutInfoCompat.Builder(compat).build();
@@ -155,6 +158,7 @@
assertEquals(disabledMessage, copyCompat.getDisabledMessage());
assertEquals(activity, copyCompat.getActivity());
assertEquals(categories, copyCompat.getCategories());
+ assertEquals(rank, copyCompat.getRank());
}
@Test
@@ -166,7 +170,7 @@
Set<String> categories = new HashSet<>();
categories.add("cat1");
categories.add("cat2");
-
+ int rank = 3;
ShortcutInfo.Builder builder = new ShortcutInfo.Builder(mContext, TEST_SHORTCUT_ID);
ShortcutInfo shortcut = builder.setIntent(mAction)
.setShortLabel(TEST_SHORTCUT_SHORT_LABEL)
@@ -174,6 +178,7 @@
.setCategories(categories)
.setDisabledMessage(disabledMessage)
.setLongLabel(longLabel)
+ .setRank(rank)
.build();
ShortcutInfoCompat compat = new ShortcutInfoCompat.Builder(mContext, shortcut).build();
@@ -185,6 +190,7 @@
assertEquals(disabledMessage, compat.getDisabledMessage());
assertEquals(activity, compat.getActivity());
assertEquals(categories, compat.getCategories());
+ assertEquals(rank, compat.getRank());
}
@Test
@@ -195,12 +201,13 @@
Set<String> categories = new HashSet<>();
categories.add("cat1");
categories.add("cat2");
-
+ int rank = 3;
ShortcutInfoCompat compat = mBuilder
.setActivity(activity)
.setCategories(categories)
.setDisabledMessage(disabledMessage)
.setLongLabel(longLabel)
+ .setRank(3)
.build();
assertEquals(TEST_SHORTCUT_ID, compat.getId());
assertEquals(TEST_SHORTCUT_SHORT_LABEL, compat.getShortLabel());
@@ -209,6 +216,7 @@
assertEquals(disabledMessage, compat.getDisabledMessage());
assertEquals(activity, compat.getActivity());
assertEquals(categories, compat.getCategories());
+ assertEquals(rank, compat.getRank());
}
@Test
@@ -220,12 +228,13 @@
Set<String> categories = new HashSet<>();
categories.add("cat1");
categories.add("cat2");
-
+ int rank = 3;
ShortcutInfoCompat compat = mBuilder
.setActivity(activity)
.setCategories(categories)
.setDisabledMessage(disabledMessage)
.setLongLabel(longLabel)
+ .setRank(3)
.build();
ShortcutInfo shortcut = compat.toShortcutInfo();
@@ -238,6 +247,7 @@
assertEquals(disabledMessage, shortcut.getDisabledMessage());
assertEquals(activity, shortcut.getActivity());
assertEquals(categories, shortcut.getCategories());
+ assertEquals(rank, shortcut.getRank());
}
@Test
@@ -249,7 +259,7 @@
ShortcutInfoCompat compat = mBuilder
.setPersons(persons)
- .setLongLived()
+ .setLongLived(true)
.build();
ShortcutInfo shortcut = compat.toShortcutInfo();
diff --git a/core/core/src/main/java/androidx/core/content/pm/ShortcutInfoCompat.java b/core/core/src/main/java/androidx/core/content/pm/ShortcutInfoCompat.java
index e5f4ef9..a8a8963 100644
--- a/core/core/src/main/java/androidx/core/content/pm/ShortcutInfoCompat.java
+++ b/core/core/src/main/java/androidx/core/content/pm/ShortcutInfoCompat.java
@@ -67,6 +67,8 @@
// TODO: Support |auto| when the value of mIsLongLived is not set
boolean mIsLongLived;
+ int mRank;
+
ShortcutInfoCompat() { }
/**
@@ -92,6 +94,7 @@
if (mCategories != null) {
builder.setCategories(mCategories);
}
+ builder.setRank(mRank);
if (Build.VERSION.SDK_INT >= 29) {
if (mPersons != null && mPersons.length > 0) {
@@ -241,6 +244,15 @@
}
/**
+ * Returns the rank of the shortcut set with {@link Builder#setRank(int)}.
+ *
+ * @see Builder#setRank(int)
+ */
+ public int getRank() {
+ return mRank;
+ }
+
+ /**
* @hide
*/
@RestrictTo(LIBRARY_GROUP_PREFIX)
@@ -311,6 +323,7 @@
mInfo.mIcon = shortcutInfo.mIcon;
mInfo.mIsAlwaysBadged = shortcutInfo.mIsAlwaysBadged;
mInfo.mIsLongLived = shortcutInfo.mIsLongLived;
+ mInfo.mRank = shortcutInfo.mRank;
if (shortcutInfo.mPersons != null) {
mInfo.mPersons = Arrays.copyOf(shortcutInfo.mPersons, shortcutInfo.mPersons.length);
}
@@ -336,6 +349,7 @@
mInfo.mDisabledMessage = shortcutInfo.getDisabledMessage();
mInfo.mCategories = shortcutInfo.getCategories();
mInfo.mPersons = ShortcutInfoCompat.getPersonsFromExtra(shortcutInfo.getExtras());
+ mInfo.mRank = shortcutInfo.getRank();
}
/**
@@ -480,13 +494,35 @@
}
/**
+ * @deprecated Use {@ink #setLongLived(boolean)) instead.
+ */
+ @Deprecated
+ @NonNull
+ public Builder setLongLived() {
+ mInfo.mIsLongLived = true;
+ return this;
+ }
+
+ /**
* Sets if a shortcut would be valid even if it has been unpublished/invisible by the app
* (as a dynamic or pinned shortcut). If it is long lived, it can be cached by various
* system services even after it has been unpublished as a dynamic shortcut.
*/
@NonNull
- public Builder setLongLived() {
- mInfo.mIsLongLived = true;
+ public Builder setLongLived(boolean longLived) {
+ mInfo.mIsLongLived = longLived;
+ return this;
+ }
+
+ /**
+ * Sets rank of a shortcut, which is a non-negative value that's used by the system to sort
+ * shortcuts. Lower value means higher importance.
+ *
+ * @see ShortcutInfo#getRank() for details.
+ */
+ @NonNull
+ public Builder setRank(int rank) {
+ mInfo.mRank = rank;
return this;
}
diff --git a/drawerlayout/build.gradle b/drawerlayout/build.gradle
index 7caf70d..49e01cc 100644
--- a/drawerlayout/build.gradle
+++ b/drawerlayout/build.gradle
@@ -11,8 +11,8 @@
dependencies {
api("androidx.annotation:annotation:1.1.0")
- api(project(":core:core"))
- api(project(":customview"))
+ api("androidx.core:core:1.2.0-alpha03")
+ api("androidx.customview:customview:1.1.0-alpha01")
androidTestImplementation(ANDROIDX_TEST_EXT_JUNIT)
androidTestImplementation(ANDROIDX_TEST_CORE)
diff --git a/dynamic-animation/build.gradle b/dynamic-animation/build.gradle
index fe7bd58..6ad3e31 100644
--- a/dynamic-animation/build.gradle
+++ b/dynamic-animation/build.gradle
@@ -9,7 +9,7 @@
}
dependencies {
- api("androidx.core:core:1.1.0-rc01")
+ api("androidx.core:core:1.1.0")
implementation("androidx.collection:collection:1.1.0")
androidTestImplementation(ANDROIDX_TEST_EXT_JUNIT)
diff --git a/emoji/core/build.gradle b/emoji/core/build.gradle
index 0b811cb..4e13514 100644
--- a/emoji/core/build.gradle
+++ b/emoji/core/build.gradle
@@ -22,7 +22,7 @@
// treats this as local jar and package it inside the aar.
api files(configurations.repackage)
- api("androidx.core:core:1.1.0-rc01")
+ api("androidx.core:core:1.1.0")
implementation(project(':collection:collection'))
androidTestImplementation(ANDROIDX_TEST_EXT_JUNIT)
diff --git a/enterprise/feedback/api/1.0.0-beta01.txt b/enterprise/feedback/api/1.0.0-beta01.txt
new file mode 100644
index 0000000..040814c
--- /dev/null
+++ b/enterprise/feedback/api/1.0.0-beta01.txt
@@ -0,0 +1,59 @@
+// Signature format: 3.0
+package androidx.enterprise.feedback {
+
+ public abstract class KeyedAppState {
+ method public static androidx.enterprise.feedback.KeyedAppState.KeyedAppStateBuilder builder();
+ method public abstract String? getData();
+ method public abstract String getKey();
+ method public abstract String? getMessage();
+ method public abstract int getSeverity();
+ field public static final int MAX_DATA_LENGTH = 1000; // 0x3e8
+ field public static final int MAX_KEY_LENGTH = 100; // 0x64
+ field public static final int MAX_MESSAGE_LENGTH = 1000; // 0x3e8
+ field public static final int SEVERITY_ERROR = 2; // 0x2
+ field public static final int SEVERITY_INFO = 1; // 0x1
+ }
+
+ public abstract static class KeyedAppState.KeyedAppStateBuilder {
+ method public androidx.enterprise.feedback.KeyedAppState build();
+ method public abstract androidx.enterprise.feedback.KeyedAppState.KeyedAppStateBuilder setData(String?);
+ method public abstract androidx.enterprise.feedback.KeyedAppState.KeyedAppStateBuilder setKey(String);
+ method public abstract androidx.enterprise.feedback.KeyedAppState.KeyedAppStateBuilder setMessage(String?);
+ method public abstract androidx.enterprise.feedback.KeyedAppState.KeyedAppStateBuilder setSeverity(int);
+ }
+
+ public abstract class KeyedAppStatesReporter {
+ method public static androidx.enterprise.feedback.KeyedAppStatesReporter create(android.content.Context);
+ method public static androidx.enterprise.feedback.KeyedAppStatesReporter create(android.content.Context, java.util.concurrent.Executor);
+ method public abstract void setStates(java.util.Collection<androidx.enterprise.feedback.KeyedAppState!>);
+ method public abstract void setStatesImmediate(java.util.Collection<androidx.enterprise.feedback.KeyedAppState!>);
+ }
+
+ public abstract class KeyedAppStatesService extends android.app.Service {
+ ctor public KeyedAppStatesService();
+ method public android.os.IBinder onBind(android.content.Intent);
+ method public abstract void onReceive(java.util.Collection<androidx.enterprise.feedback.ReceivedKeyedAppState!>, boolean);
+ }
+
+ public abstract class ReceivedKeyedAppState {
+ method public static androidx.enterprise.feedback.ReceivedKeyedAppState.ReceivedKeyedAppStateBuilder builder();
+ method public abstract String? getData();
+ method public abstract String getKey();
+ method public abstract String? getMessage();
+ method public abstract String getPackageName();
+ method public abstract int getSeverity();
+ method public abstract long getTimestamp();
+ }
+
+ public abstract static class ReceivedKeyedAppState.ReceivedKeyedAppStateBuilder {
+ method public abstract androidx.enterprise.feedback.ReceivedKeyedAppState build();
+ method public abstract androidx.enterprise.feedback.ReceivedKeyedAppState.ReceivedKeyedAppStateBuilder setData(String?);
+ method public abstract androidx.enterprise.feedback.ReceivedKeyedAppState.ReceivedKeyedAppStateBuilder setKey(String);
+ method public abstract androidx.enterprise.feedback.ReceivedKeyedAppState.ReceivedKeyedAppStateBuilder setMessage(String?);
+ method public abstract androidx.enterprise.feedback.ReceivedKeyedAppState.ReceivedKeyedAppStateBuilder setPackageName(String);
+ method public abstract androidx.enterprise.feedback.ReceivedKeyedAppState.ReceivedKeyedAppStateBuilder setSeverity(int);
+ method public abstract androidx.enterprise.feedback.ReceivedKeyedAppState.ReceivedKeyedAppStateBuilder setTimestamp(long);
+ }
+
+}
+
diff --git a/biometric/api/res-1.0.0-beta01.txt b/enterprise/feedback/api/res-1.0.0-beta01.txt
similarity index 100%
copy from biometric/api/res-1.0.0-beta01.txt
copy to enterprise/feedback/api/res-1.0.0-beta01.txt
diff --git a/enterprise/feedback/api/restricted_1.0.0-beta01.txt b/enterprise/feedback/api/restricted_1.0.0-beta01.txt
new file mode 100644
index 0000000..040814c
--- /dev/null
+++ b/enterprise/feedback/api/restricted_1.0.0-beta01.txt
@@ -0,0 +1,59 @@
+// Signature format: 3.0
+package androidx.enterprise.feedback {
+
+ public abstract class KeyedAppState {
+ method public static androidx.enterprise.feedback.KeyedAppState.KeyedAppStateBuilder builder();
+ method public abstract String? getData();
+ method public abstract String getKey();
+ method public abstract String? getMessage();
+ method public abstract int getSeverity();
+ field public static final int MAX_DATA_LENGTH = 1000; // 0x3e8
+ field public static final int MAX_KEY_LENGTH = 100; // 0x64
+ field public static final int MAX_MESSAGE_LENGTH = 1000; // 0x3e8
+ field public static final int SEVERITY_ERROR = 2; // 0x2
+ field public static final int SEVERITY_INFO = 1; // 0x1
+ }
+
+ public abstract static class KeyedAppState.KeyedAppStateBuilder {
+ method public androidx.enterprise.feedback.KeyedAppState build();
+ method public abstract androidx.enterprise.feedback.KeyedAppState.KeyedAppStateBuilder setData(String?);
+ method public abstract androidx.enterprise.feedback.KeyedAppState.KeyedAppStateBuilder setKey(String);
+ method public abstract androidx.enterprise.feedback.KeyedAppState.KeyedAppStateBuilder setMessage(String?);
+ method public abstract androidx.enterprise.feedback.KeyedAppState.KeyedAppStateBuilder setSeverity(int);
+ }
+
+ public abstract class KeyedAppStatesReporter {
+ method public static androidx.enterprise.feedback.KeyedAppStatesReporter create(android.content.Context);
+ method public static androidx.enterprise.feedback.KeyedAppStatesReporter create(android.content.Context, java.util.concurrent.Executor);
+ method public abstract void setStates(java.util.Collection<androidx.enterprise.feedback.KeyedAppState!>);
+ method public abstract void setStatesImmediate(java.util.Collection<androidx.enterprise.feedback.KeyedAppState!>);
+ }
+
+ public abstract class KeyedAppStatesService extends android.app.Service {
+ ctor public KeyedAppStatesService();
+ method public android.os.IBinder onBind(android.content.Intent);
+ method public abstract void onReceive(java.util.Collection<androidx.enterprise.feedback.ReceivedKeyedAppState!>, boolean);
+ }
+
+ public abstract class ReceivedKeyedAppState {
+ method public static androidx.enterprise.feedback.ReceivedKeyedAppState.ReceivedKeyedAppStateBuilder builder();
+ method public abstract String? getData();
+ method public abstract String getKey();
+ method public abstract String? getMessage();
+ method public abstract String getPackageName();
+ method public abstract int getSeverity();
+ method public abstract long getTimestamp();
+ }
+
+ public abstract static class ReceivedKeyedAppState.ReceivedKeyedAppStateBuilder {
+ method public abstract androidx.enterprise.feedback.ReceivedKeyedAppState build();
+ method public abstract androidx.enterprise.feedback.ReceivedKeyedAppState.ReceivedKeyedAppStateBuilder setData(String?);
+ method public abstract androidx.enterprise.feedback.ReceivedKeyedAppState.ReceivedKeyedAppStateBuilder setKey(String);
+ method public abstract androidx.enterprise.feedback.ReceivedKeyedAppState.ReceivedKeyedAppStateBuilder setMessage(String?);
+ method public abstract androidx.enterprise.feedback.ReceivedKeyedAppState.ReceivedKeyedAppStateBuilder setPackageName(String);
+ method public abstract androidx.enterprise.feedback.ReceivedKeyedAppState.ReceivedKeyedAppStateBuilder setSeverity(int);
+ method public abstract androidx.enterprise.feedback.ReceivedKeyedAppState.ReceivedKeyedAppStateBuilder setTimestamp(long);
+ }
+
+}
+
diff --git a/enterprise/feedback/testing/api/1.0.0-beta01.txt b/enterprise/feedback/testing/api/1.0.0-beta01.txt
new file mode 100644
index 0000000..9c90bc0
--- /dev/null
+++ b/enterprise/feedback/testing/api/1.0.0-beta01.txt
@@ -0,0 +1,17 @@
+// Signature format: 3.0
+package androidx.enterprise.feedback {
+
+ public class FakeKeyedAppStatesReporter extends androidx.enterprise.feedback.KeyedAppStatesReporter {
+ method public java.util.List<androidx.enterprise.feedback.KeyedAppState!> getKeyedAppStates();
+ method public java.util.Map<java.lang.String!,androidx.enterprise.feedback.KeyedAppState!> getKeyedAppStatesByKey();
+ method public int getNumberOfUploads();
+ method public java.util.List<androidx.enterprise.feedback.KeyedAppState!> getOnDeviceKeyedAppStates();
+ method public java.util.Map<java.lang.String!,androidx.enterprise.feedback.KeyedAppState!> getOnDeviceKeyedAppStatesByKey();
+ method public java.util.List<androidx.enterprise.feedback.KeyedAppState!> getUploadedKeyedAppStates();
+ method public java.util.Map<java.lang.String!,androidx.enterprise.feedback.KeyedAppState!> getUploadedKeyedAppStatesByKey();
+ method public void setStates(java.util.Collection<androidx.enterprise.feedback.KeyedAppState!>);
+ method public void setStatesImmediate(java.util.Collection<androidx.enterprise.feedback.KeyedAppState!>);
+ }
+
+}
+
diff --git a/biometric/api/res-1.0.0-beta01.txt b/enterprise/feedback/testing/api/res-1.0.0-beta01.txt
similarity index 100%
copy from biometric/api/res-1.0.0-beta01.txt
copy to enterprise/feedback/testing/api/res-1.0.0-beta01.txt
diff --git a/enterprise/feedback/testing/api/restricted_1.0.0-beta01.txt b/enterprise/feedback/testing/api/restricted_1.0.0-beta01.txt
new file mode 100644
index 0000000..9c90bc0
--- /dev/null
+++ b/enterprise/feedback/testing/api/restricted_1.0.0-beta01.txt
@@ -0,0 +1,17 @@
+// Signature format: 3.0
+package androidx.enterprise.feedback {
+
+ public class FakeKeyedAppStatesReporter extends androidx.enterprise.feedback.KeyedAppStatesReporter {
+ method public java.util.List<androidx.enterprise.feedback.KeyedAppState!> getKeyedAppStates();
+ method public java.util.Map<java.lang.String!,androidx.enterprise.feedback.KeyedAppState!> getKeyedAppStatesByKey();
+ method public int getNumberOfUploads();
+ method public java.util.List<androidx.enterprise.feedback.KeyedAppState!> getOnDeviceKeyedAppStates();
+ method public java.util.Map<java.lang.String!,androidx.enterprise.feedback.KeyedAppState!> getOnDeviceKeyedAppStatesByKey();
+ method public java.util.List<androidx.enterprise.feedback.KeyedAppState!> getUploadedKeyedAppStates();
+ method public java.util.Map<java.lang.String!,androidx.enterprise.feedback.KeyedAppState!> getUploadedKeyedAppStatesByKey();
+ method public void setStates(java.util.Collection<androidx.enterprise.feedback.KeyedAppState!>);
+ method public void setStatesImmediate(java.util.Collection<androidx.enterprise.feedback.KeyedAppState!>);
+ }
+
+}
+
diff --git a/fragment/fragment-ktx/build.gradle b/fragment/fragment-ktx/build.gradle
index 45b7557..7a021ff 100644
--- a/fragment/fragment-ktx/build.gradle
+++ b/fragment/fragment-ktx/build.gradle
@@ -38,7 +38,7 @@
api(project(":activity:activity-ktx")) {
because 'Mirror fragment dependency graph for -ktx artifacts'
}
- api("androidx.core:core-ktx:1.1.0-rc01") {
+ api("androidx.core:core-ktx:1.1.0") {
because 'Mirror fragment dependency graph for -ktx artifacts'
}
api("androidx.collection:collection-ktx:1.1.0") {
diff --git a/fragment/fragment/build.gradle b/fragment/fragment/build.gradle
index 669429a..777cb59 100644
--- a/fragment/fragment/build.gradle
+++ b/fragment/fragment/build.gradle
@@ -18,7 +18,7 @@
dependencies {
api("androidx.annotation:annotation:1.1.0")
- api("androidx.core:core:1.1.0-rc01")
+ api("androidx.core:core:1.1.0")
api("androidx.collection:collection:1.1.0")
api("androidx.viewpager:viewpager:1.0.0")
api("androidx.loader:loader:1.0.0")
diff --git a/fragment/fragment/src/androidTest/java/androidx/fragment/app/FragmentTestUtil.kt b/fragment/fragment/src/androidTest/java/androidx/fragment/app/FragmentTestUtil.kt
index 0f949a9..c3f8168 100644
--- a/fragment/fragment/src/androidTest/java/androidx/fragment/app/FragmentTestUtil.kt
+++ b/fragment/fragment/src/androidTest/java/androidx/fragment/app/FragmentTestUtil.kt
@@ -15,12 +15,16 @@
*/
package androidx.fragment.app
+import android.graphics.Rect
import android.os.Build
+import android.view.View
import android.view.ViewGroup
import androidx.annotation.LayoutRes
+import androidx.fragment.test.R
import androidx.test.platform.app.InstrumentationRegistry
import androidx.test.rule.ActivityTestRule
import androidx.testutils.runOnUiThreadRethrow
+import com.google.common.truth.Truth.assertThat
import com.google.common.truth.Truth.assertWithMessage
import java.lang.ref.WeakReference
import java.util.ArrayList
@@ -83,6 +87,75 @@
}
}
+// Transition test methods start
+fun ActivityTestRule<out FragmentActivity>.findGreen(): View {
+ return activity.findViewById(R.id.greenSquare)
+}
+
+fun ActivityTestRule<out FragmentActivity>.findBlue(): View {
+ return activity.findViewById(R.id.blueSquare)
+}
+
+fun ActivityTestRule<out FragmentActivity>.findRed(): View? {
+ return activity.findViewById(R.id.redSquare)
+}
+
+fun verifyAndClearTransition(
+ transition: TargetTracking,
+ epicenter: Rect?,
+ vararg expected: View
+) {
+ if (epicenter == null) {
+ assertThat(transition.capturedEpicenter).isNull()
+ } else {
+ assertThat(transition.capturedEpicenter).isEqualTo(epicenter)
+ }
+ val targets = transition.trackedTargets
+ val sb = StringBuilder()
+ sb.append("Expected: [")
+ .append(expected.size)
+ .append("] {")
+ var isFirst = true
+ for (view in expected) {
+ if (isFirst) {
+ isFirst = false
+ } else {
+ sb.append(", ")
+ }
+ sb.append(view)
+ }
+ sb.append("}, but got: [")
+ .append(targets.size)
+ .append("] {")
+ isFirst = true
+ for (view in targets) {
+ if (isFirst) {
+ isFirst = false
+ } else {
+ sb.append(", ")
+ }
+ sb.append(view)
+ }
+ sb.append("}")
+ val errorMessage = sb.toString()
+
+ assertWithMessage(errorMessage).that(targets.size).isEqualTo(expected.size)
+ for (view in expected) {
+ assertWithMessage(errorMessage).that(targets.contains(view)).isTrue()
+ }
+ transition.clearTargets()
+}
+
+fun verifyNoOtherTransitions(fragment: TransitionFragment) {
+ assertThat(fragment.enterTransition.targets.size).isEqualTo(0)
+ assertThat(fragment.exitTransition.targets.size).isEqualTo(0)
+ assertThat(fragment.reenterTransition.targets.size).isEqualTo(0)
+ assertThat(fragment.returnTransition.targets.size).isEqualTo(0)
+ assertThat(fragment.sharedElementEnter.targets.size).isEqualTo(0)
+ assertThat(fragment.sharedElementReturn.targets.size).isEqualTo(0)
+}
+// Transition test methods end
+
/**
* Allocates until a garbage collection occurs.
*/
diff --git a/fragment/fragment/src/androidTest/java/androidx/fragment/app/FragmentTransitionAnimTest.kt b/fragment/fragment/src/androidTest/java/androidx/fragment/app/FragmentTransitionAnimTest.kt
new file mode 100644
index 0000000..d3717d2
--- /dev/null
+++ b/fragment/fragment/src/androidTest/java/androidx/fragment/app/FragmentTransitionAnimTest.kt
@@ -0,0 +1,301 @@
+/*
+ * 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.animation.Animator
+import android.animation.AnimatorListenerAdapter
+import android.animation.ValueAnimator
+import android.os.Build
+import android.os.Bundle
+import android.transition.ChangeBounds
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import android.view.animation.Animation
+import android.view.animation.TranslateAnimation
+import androidx.annotation.AnimRes
+import androidx.fragment.app.test.FragmentTestActivity
+import androidx.fragment.test.R
+import androidx.lifecycle.Lifecycle
+import androidx.test.filters.MediumTest
+import androidx.test.filters.SdkSuppress
+import androidx.test.rule.ActivityTestRule
+import androidx.testutils.waitForExecution
+import com.google.common.truth.Truth.assertThat
+import org.junit.After
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.Parameterized
+import java.util.concurrent.CountDownLatch
+import java.util.concurrent.TimeUnit
+
+@MediumTest
+@RunWith(Parameterized::class)
+@SdkSuppress(minSdkVersion = Build.VERSION_CODES.LOLLIPOP)
+class FragmentTransitionAnimTest(private val reorderingAllowed: Boolean) {
+
+ @get:Rule
+ val activityRule = ActivityTestRule(FragmentTestActivity::class.java)
+
+ private lateinit var fragmentManager: FragmentManager
+ private var onBackStackChangedTimes: Int = 0
+ private val onBackStackChangedListener =
+ FragmentManager.OnBackStackChangedListener { onBackStackChangedTimes++ }
+
+ @Before
+ fun setup() {
+ activityRule.setContentView(R.layout.simple_container)
+ onBackStackChangedTimes = 0
+ fragmentManager = activityRule.activity.supportFragmentManager
+ fragmentManager.addOnBackStackChangedListener(onBackStackChangedListener)
+ }
+
+ @After
+ fun teardown() {
+ fragmentManager.removeOnBackStackChangedListener(onBackStackChangedListener)
+ }
+
+ // Ensure when transition duration is longer than animation duration, we will get both end
+ // callbacks
+ @Test
+ fun transitionShorterThanAnimation() {
+ val fragment = TransitionAnimationFragment()
+ fragmentManager.beginTransaction()
+ .setReorderingAllowed(reorderingAllowed)
+ .setCustomAnimations(ENTER, EXIT)
+ .add(R.id.fragmentContainer, fragment)
+ .addToBackStack(null)
+ .commit()
+ activityRule.waitForExecution()
+ assertThat(onBackStackChangedTimes).isEqualTo(1)
+ fragment.waitForTransition()
+ verifyAndClearTransition(fragment.enterTransition, null, activityRule.findBlue(),
+ activityRule.findGreen())
+ verifyNoOtherTransitions(fragment)
+
+ val changeBoundsExitTransition = ChangeBounds().apply {
+ duration = 100
+ }
+ fragment.setExitTransition(changeBoundsExitTransition)
+ changeBoundsExitTransition.addListener(fragment.listener)
+
+ // exit transition
+ fragmentManager.beginTransaction()
+ .setReorderingAllowed(reorderingAllowed)
+ .setCustomAnimations(ENTER, EXIT)
+ .remove(fragment)
+ .addToBackStack(null)
+ .commit()
+ activityRule.waitForExecution()
+
+ fragment.waitForTransition()
+ assertThat(fragment.exitAnimationLatch.await(1000, TimeUnit.MILLISECONDS)).isTrue()
+ assertThat(onBackStackChangedTimes).isEqualTo(2)
+ }
+
+ // Ensure when transition duration is shorter than animation duration, we will get both end
+ // callbacks
+ @Test
+ fun transitionLongerThanAnimation() {
+ val fragment = TransitionAnimationFragment()
+ fragmentManager.beginTransaction()
+ .setReorderingAllowed(reorderingAllowed)
+ .setCustomAnimations(ENTER, EXIT)
+ .add(R.id.fragmentContainer, fragment)
+ .addToBackStack(null)
+ .commit()
+ activityRule.waitForExecution()
+ assertThat(onBackStackChangedTimes).isEqualTo(1)
+
+ fragment.waitForTransition()
+ verifyAndClearTransition(fragment.enterTransition, null, activityRule.findBlue(),
+ activityRule.findGreen())
+ verifyNoOtherTransitions(fragment)
+
+ val changeBoundsExitTransition = ChangeBounds().apply {
+ duration = 1000
+ }
+ fragment.setExitTransition(changeBoundsExitTransition)
+ changeBoundsExitTransition.addListener(fragment.listener)
+
+ // exit transition
+ fragmentManager.beginTransaction()
+ .setReorderingAllowed(reorderingAllowed)
+ .setCustomAnimations(ENTER, EXIT)
+ .remove(fragment)
+ .addToBackStack(null)
+ .commit()
+ activityRule.waitForExecution()
+
+ fragment.waitForTransition()
+ assertThat(fragment.exitAnimationLatch.await(1000, TimeUnit.MILLISECONDS)).isTrue()
+ assertThat(onBackStackChangedTimes).isEqualTo(2)
+ }
+
+ // Ensure when transition duration is shorter than animator duration, we will get both end
+ // callbacks
+ @Test
+ fun transitionShorterThanAnimator() {
+ val fragment = TransitionAnimatorFragment()
+ fragment.exitTransition.duration = 100
+ fragmentManager.beginTransaction()
+ .setReorderingAllowed(reorderingAllowed)
+ .setCustomAnimations(ENTER, EXIT)
+ .add(R.id.fragmentContainer, fragment)
+ .addToBackStack(null)
+ .commit()
+ activityRule.waitForExecution()
+ assertThat(onBackStackChangedTimes).isEqualTo(1)
+ fragment.waitForTransition()
+ verifyAndClearTransition(fragment.enterTransition, null, activityRule.findBlue(),
+ activityRule.findGreen())
+ verifyNoOtherTransitions(fragment)
+
+ val changeBoundsExitTransition = ChangeBounds().apply {
+ duration = 100
+ }
+ fragment.setExitTransition(changeBoundsExitTransition)
+ changeBoundsExitTransition.addListener(fragment.listener)
+
+ // exit transition
+ fragmentManager.beginTransaction()
+ .setReorderingAllowed(reorderingAllowed)
+ .setCustomAnimations(ENTER, EXIT)
+ .remove(fragment)
+ .addToBackStack(null)
+ .commit()
+ activityRule.waitForExecution()
+
+ fragment.waitForTransition()
+ assertThat(fragment.exitAnimatorLatch.await(1000, TimeUnit.MILLISECONDS)).isTrue()
+ assertThat(onBackStackChangedTimes).isEqualTo(2)
+ }
+
+ // Ensure when transition duration is longer than animator duration, we will get both end
+ // callbacks
+ @Test
+ fun transitionLongerThanAnimator() {
+ val fragment = TransitionAnimatorFragment()
+ fragment.exitTransition.duration = 1000
+ fragmentManager.beginTransaction()
+ .setReorderingAllowed(reorderingAllowed)
+ .setCustomAnimations(ENTER, EXIT)
+ .add(R.id.fragmentContainer, fragment)
+ .addToBackStack(null)
+ .commit()
+ activityRule.waitForExecution()
+ assertThat(onBackStackChangedTimes).isEqualTo(1)
+ fragment.waitForTransition()
+ verifyAndClearTransition(fragment.enterTransition, null, activityRule.findBlue(),
+ activityRule.findGreen())
+ verifyNoOtherTransitions(fragment)
+
+ val changeBoundsExitTransition = ChangeBounds().apply {
+ duration = 1000
+ }
+ fragment.setExitTransition(changeBoundsExitTransition)
+ changeBoundsExitTransition.addListener(fragment.listener)
+
+ // exit transition
+ fragmentManager.beginTransaction()
+ .setReorderingAllowed(reorderingAllowed)
+ .setCustomAnimations(ENTER, EXIT)
+ .remove(fragment)
+ .addToBackStack(null)
+ .commit()
+ activityRule.waitForExecution()
+
+ fragment.waitForTransition()
+ assertThat(fragment.exitAnimatorLatch.await(1000, TimeUnit.MILLISECONDS)).isTrue()
+ assertThat(onBackStackChangedTimes).isEqualTo(2)
+ }
+
+ class TransitionAnimationFragment : TransitionFragment(R.layout.scene1) {
+ lateinit var createdView: View
+ val exitAnimationLatch = CountDownLatch(1)
+
+ override fun onCreateView(
+ inflater: LayoutInflater,
+ container: ViewGroup?,
+ savedInstanceState: Bundle?
+ ) = super.onCreateView(inflater, container, savedInstanceState)?.apply {
+ createdView = this
+ }
+
+ override fun onCreateAnimation(transit: Int, enter: Boolean, nextAnim: Int): Animation? {
+ if (nextAnim == 0) {
+ return null
+ }
+
+ return TranslateAnimation(-10f, 0f, 0f, 0f).apply {
+ duration = 300
+ setAnimationListener(object : Animation.AnimationListener {
+ override fun onAnimationStart(animation: Animation) { }
+
+ override fun onAnimationEnd(animation: Animation) {
+ if (viewLifecycleOwner.lifecycle.currentState != Lifecycle.State.DESTROYED
+ ) {
+ if (!enter) {
+ // When exiting, the view is detached after onAnimationEnd,
+ // so wait one frame to count down the latch
+ createdView.post { exitAnimationLatch.countDown() }
+ }
+ }
+ }
+ override fun onAnimationRepeat(animation: Animation) {}
+ })
+ }
+ }
+ }
+
+ class TransitionAnimatorFragment : TransitionFragment(R.layout.scene1) {
+ lateinit var exitAnimatorLatch: CountDownLatch
+
+ override fun onCreateAnimator(
+ transit: Int,
+ enter: Boolean,
+ nextAnim: Int
+ ) = ValueAnimator.ofFloat(0f, 1f).setDuration(300)?.apply {
+ if (nextAnim == 0) {
+ return null
+ }
+ addListener(object : AnimatorListenerAdapter() {
+ override fun onAnimationEnd(animation: Animator) {
+ if (!enter) {
+ exitAnimatorLatch.countDown()
+ }
+ }
+ })
+ exitAnimatorLatch = CountDownLatch(1)
+ }
+ }
+
+ companion object {
+ @JvmStatic
+ @Parameterized.Parameters
+ fun data(): Array<Boolean> {
+ return arrayOf(false, true)
+ }
+
+ @AnimRes
+ private val ENTER = 1
+ @AnimRes
+ private val EXIT = 2
+ }
+}
\ No newline at end of file
diff --git a/fragment/fragment/src/androidTest/java/androidx/fragment/app/FragmentTransitionTest.kt b/fragment/fragment/src/androidTest/java/androidx/fragment/app/FragmentTransitionTest.kt
index 8f852ff..58aace3 100644
--- a/fragment/fragment/src/androidTest/java/androidx/fragment/app/FragmentTransitionTest.kt
+++ b/fragment/fragment/src/androidTest/java/androidx/fragment/app/FragmentTransitionTest.kt
@@ -29,7 +29,6 @@
import androidx.test.rule.ActivityTestRule
import androidx.testutils.waitForExecution
import com.google.common.truth.Truth.assertThat
-import com.google.common.truth.Truth.assertWithMessage
import org.junit.After
import org.junit.Assert.fail
import org.junit.Before
@@ -75,8 +74,8 @@
fun enterExitTransitions() {
// enter transition
val fragment = setupInitialFragment()
- val blue = findBlue()
- val green = findBlue()
+ val blue = activityRule.findBlue()
+ val green = activityRule.findBlue()
// exit transition
fragmentManager.beginTransaction()
@@ -93,8 +92,8 @@
// reenter transition
activityRule.popBackStackImmediate()
fragment.waitForTransition()
- val green2 = findGreen()
- val blue2 = findBlue()
+ val green2 = activityRule.findGreen()
+ val blue2 = activityRule.findBlue()
verifyAndClearTransition(fragment.reenterTransition, null, green2, blue2)
verifyNoOtherTransitions(fragment)
assertThat(onBackStackChangedTimes).isEqualTo(3)
@@ -107,6 +106,77 @@
assertThat(onBackStackChangedTimes).isEqualTo(4)
}
+ // Test removing a Fragment with a Transition and adding it back before the Transition
+ // finishes is handled correctly.
+ @Test
+ fun removeThenAddBeforeTransitionFinishes() {
+ // enter transition
+ val fragment = setupInitialFragment()
+ val blue = activityRule.findBlue()
+ val green = activityRule.findGreen()
+
+ val view1 = fragment.view
+
+ activityRule.runOnUiThread {
+ // exit transition
+ fragmentManager.beginTransaction()
+ .setReorderingAllowed(reorderingAllowed)
+ .remove(fragment)
+ .addToBackStack(null)
+ .commit()
+
+ fragmentManager.beginTransaction()
+ .setReorderingAllowed(reorderingAllowed)
+ .add(R.id.fragmentContainer, fragment)
+ .addToBackStack(null)
+ .commit()
+ }
+ activityRule.waitForExecution()
+
+ // If reordering is allowed, the remove is ignored and the transaction is just added to the
+ // back stack
+ if (reorderingAllowed) {
+ assertThat(onBackStackChangedTimes).isEqualTo(2)
+ assertThat(fragment.requireView()).isEqualTo(view1)
+ } else {
+ // If reorder is not allowed we will get the exit Transition and the fragment will be
+ // added with a different view.
+ fragment.waitForTransition()
+ verifyAndClearTransition(fragment.exitTransition, null, green, blue)
+ assertThat(onBackStackChangedTimes).isEqualTo(3)
+ assertThat(fragment.requireView()).isNotEqualTo(view1)
+ }
+ verifyNoOtherTransitions(fragment)
+ }
+
+ @Test
+ fun ensureTransitionsFinishBeforeViewDestroyed() {
+ // enter transition
+ val fragment = TransitionFinishFirstFragment()
+ fragmentManager.beginTransaction()
+ .setReorderingAllowed(reorderingAllowed)
+ .add(R.id.fragmentContainer, fragment)
+ .addToBackStack(null)
+ .commit()
+ activityRule.waitForExecution()
+ assertThat(onBackStackChangedTimes).isEqualTo(1)
+ fragment.waitForTransition()
+ val blueSquare1 = activityRule.findBlue()
+ val greenSquare1 = activityRule.findGreen()
+ verifyAndClearTransition(fragment.enterTransition, null, blueSquare1, greenSquare1)
+ verifyNoOtherTransitions(fragment)
+
+ // Ensure that our countdown latch has been reset for the Fragment
+ assertThat(fragment.endTransitionCountDownLatch.count).isEqualTo(1)
+
+ fragmentManager.beginTransaction()
+ .setReorderingAllowed(reorderingAllowed)
+ .replace(R.id.fragmentContainer, TransitionFragment())
+ .addToBackStack(null)
+ .commit()
+ activityRule.waitForExecution()
+ }
+
// Test that shared elements transition from one fragment to the next
// and back during pop.
@Test
@@ -144,8 +214,8 @@
fun removeAdded() {
val fragment1 = setupInitialFragment()
- val startBlue = findBlue()
- val startGreen = findGreen()
+ val startBlue = activityRule.findBlue()
+ val startGreen = activityRule.findGreen()
val fragment2 = TransitionFragment(R.layout.scene2)
@@ -163,8 +233,8 @@
// should be a normal transition from fragment1 to fragment2
fragment2.waitForTransition()
- val endBlue = findBlue()
- val endGreen = findGreen()
+ val endBlue = activityRule.findBlue()
+ val endGreen = activityRule.findGreen()
verifyAndClearTransition(fragment1.exitTransition, null, startBlue, startGreen)
verifyAndClearTransition(fragment2.enterTransition, null, endBlue, endGreen)
verifyNoOtherTransitions(fragment1)
@@ -176,8 +246,8 @@
fragment1.waitForTransition()
fragment2.waitForTransition()
- val popBlue = findBlue()
- val popGreen = findGreen()
+ val popBlue = activityRule.findBlue()
+ val popGreen = activityRule.findGreen()
verifyAndClearTransition(fragment1.reenterTransition, null, popBlue, popGreen)
verifyAndClearTransition(fragment2.returnTransition, null, endBlue, endGreen)
verifyNoOtherTransitions(fragment1)
@@ -231,7 +301,7 @@
val enterCallback = mock(SharedElementCallback::class.java)
fragment2.setEnterSharedElementCallback(enterCallback)
- val startBlue = findBlue()
+ val startBlue = activityRule.findBlue()
verifyTransition(fragment1, fragment2, "blueSquare")
@@ -248,7 +318,7 @@
assertThat(names.value[0]).isEqualTo("blueSquare")
assertThat(views.value[0]).isEqualTo(startBlue)
- val endBlue = findBlue()
+ val endBlue = activityRule.findBlue()
verify(enterCallback).onSharedElementEnd(
names.capture(), views.capture(),
@@ -274,7 +344,7 @@
assertThat(names.value[0]).isEqualTo("blueSquare")
assertThat(views.value[0]).isEqualTo(endBlue)
- val reenterBlue = findBlue()
+ val reenterBlue = activityRule.findBlue()
verify(enterCallback).onSharedElementEnd(
names.capture(), views.capture(),
@@ -295,8 +365,8 @@
// Now do a transition to scene2
val fragment2 = TransitionFragment(R.layout.scene2)
- val startBlue = findBlue()
- val startGreen = findGreen()
+ val startBlue = activityRule.findBlue()
+ val startGreen = activityRule.findGreen()
val startGreenBounds = getBoundsOnScreen(startGreen)
@@ -325,7 +395,7 @@
fragment1.waitForTransition()
fragment2.waitForTransition()
- val endBlue = findBlue()
+ val endBlue = activityRule.findBlue()
val endBlueBounds = getBoundsOnScreen(endBlue)
verifyAndClearTransition(
@@ -354,7 +424,7 @@
fragment1.waitForTransition()
fragment2.waitForTransition()
- val reenterGreen = findGreen()
+ val reenterGreen = activityRule.findGreen()
verifyAndClearTransition(
fragment2.sharedElementReturn, endBlueBounds, endBlue,
reenterGreen
@@ -369,7 +439,7 @@
// Now do a transition to scene2
val fragment2 = TransitionFragment(R.layout.scene2)
- val startBlue = findBlue()
+ val startBlue = activityRule.findBlue()
val startBlueBounds = getBoundsOnScreen(startBlue)
val mapIn = object : SharedElementCallback() {
@@ -399,8 +469,8 @@
fragment1.waitForTransition()
fragment2.waitForTransition()
- val endGreen = findGreen()
- val endBlue = findBlue()
+ val endGreen = activityRule.findGreen()
+ val endBlue = activityRule.findBlue()
val endGreenBounds = getBoundsOnScreen(endGreen)
verifyAndClearTransition(
@@ -427,7 +497,7 @@
fragment1.waitForTransition()
fragment2.waitForTransition()
- val reenterBlue = findBlue()
+ val reenterBlue = activityRule.findBlue()
verifyAndClearTransition(
fragment2.sharedElementReturn, endGreenBounds, endGreen,
reenterBlue
@@ -442,8 +512,8 @@
// Now do a transition to scene2
val fragment2 = ComplexTransitionFragment()
- val startBlue = findBlue()
- val startGreen = findGreen()
+ val startBlue = activityRule.findBlue()
+ val startGreen = activityRule.findGreen()
val startBlueBounds = getBoundsOnScreen(startBlue)
fragmentManager.beginTransaction()
@@ -459,8 +529,8 @@
fragment1.waitForTransition()
fragment2.waitForTransition()
- val endBlue = findBlue()
- val endGreen = findGreen()
+ val endBlue = activityRule.findBlue()
+ val endGreen = activityRule.findGreen()
val endBlueBounds = getBoundsOnScreen(endBlue)
verifyAndClearTransition(
@@ -479,8 +549,8 @@
fragment1.waitForTransition()
fragment2.waitForTransition()
- val reenterBlue = findBlue()
- val reenterGreen = findGreen()
+ val reenterBlue = activityRule.findBlue()
+ val reenterGreen = activityRule.findGreen()
verifyAndClearTransition(
fragment2.sharedElementReturnTransition1, endBlueBounds,
@@ -526,8 +596,8 @@
val fragment1 = setupInitialFragment()
val fragment2 = TransitionFragment(R.layout.scene2)
- val startBlue = findBlue()
- val startGreen = findGreen()
+ val startBlue = activityRule.findBlue()
+ val startGreen = activityRule.findGreen()
fragmentManager.beginTransaction()
.setReorderingAllowed(reorderingAllowed)
@@ -576,8 +646,8 @@
val fragment1 = setupInitialFragment()
val fragment2 = TransitionFragment(R.layout.scene2)
- val startBlue = findBlue()
- val startGreen = findGreen()
+ val startBlue = activityRule.findBlue()
+ val startGreen = activityRule.findGreen()
fragmentManager.beginTransaction()
.setReorderingAllowed(reorderingAllowed)
@@ -601,8 +671,8 @@
activityRule.waitForExecution()
- val reenterBlue = findBlue()
- val reenterGreen = findGreen()
+ val reenterBlue = activityRule.findBlue()
+ val reenterGreen = activityRule.findGreen()
verifyAndClearTransition(fragment1.reenterTransition, null, reenterGreen, reenterBlue)
verifyNoOtherTransitions(fragment1)
@@ -619,8 +689,8 @@
// Now do a transition to scene2
val fragment2 = TransitionFragment(R.layout.scene2)
- val startBlue = findBlue()
- val startGreen = findGreen()
+ val startBlue = activityRule.findBlue()
+ val startGreen = activityRule.findGreen()
val startBlueBounds = getBoundsOnScreen(startBlue)
fragmentManager.beginTransaction()
@@ -634,8 +704,8 @@
fragment1.waitForTransition()
fragment2.waitForTransition()
- val endBlue = findBlue()
- val endGreen = findGreen()
+ val endBlue = activityRule.findBlue()
+ val endGreen = activityRule.findGreen()
if (reorderingAllowed) {
verifyAndClearTransition(fragment1.exitTransition, null, startGreen, startBlue)
@@ -654,8 +724,8 @@
fun sharedDuplicateTargetNames() {
setupInitialFragment()
- val startBlue = findBlue()
- val startGreen = findGreen()
+ val startBlue = activityRule.findBlue()
+ val startGreen = activityRule.findGreen()
val ft = fragmentManager.beginTransaction()
ft.addSharedElement(startBlue, "blueSquare")
@@ -721,8 +791,8 @@
fun noSharedElementTransition() {
val fragment1 = setupInitialFragment()
- val startBlue = findBlue()
- val startGreen = findGreen()
+ val startBlue = activityRule.findBlue()
+ val startGreen = activityRule.findGreen()
val startBlueBounds = getBoundsOnScreen(startBlue)
val fragment2 = TransitionFragment(R.layout.scene2)
@@ -736,8 +806,8 @@
fragment1.waitForTransition()
fragment2.waitForTransition()
- val midGreen = findGreen()
- val midBlue = findBlue()
+ val midGreen = activityRule.findGreen()
+ val midBlue = activityRule.findBlue()
val midBlueBounds = getBoundsOnScreen(midBlue)
verifyAndClearTransition(fragment1.exitTransition, startBlueBounds, startGreen)
verifyAndClearTransition(fragment2.sharedElementEnter, startBlueBounds, startBlue, midBlue)
@@ -766,9 +836,9 @@
// reordering allowed fragment3 to get a transition so we should wait for it to finish
fragment3.waitForTransition()
verifyAndClearTransition(fragment2.returnTransition, null, midGreen, midBlue)
- val endGreen = findGreen()
- val endBlue = findBlue()
- val endRed = findRed()
+ val endGreen = activityRule.findGreen()
+ val endBlue = activityRule.findBlue()
+ val endRed = activityRule.findRed()
verifyAndClearTransition(fragment3.enterTransition, null, endGreen, endBlue, endRed!!)
verifyNoOtherTransitions(fragment2)
verifyNoOtherTransitions(fragment3)
@@ -788,8 +858,8 @@
fun noSharedElementTransitionOnPop() {
val fragment1 = setupInitialFragment()
- val startBlue = findBlue()
- val startGreen = findGreen()
+ val startBlue = activityRule.findBlue()
+ val startGreen = activityRule.findGreen()
val startGreenBounds = getBoundsOnScreen(startGreen)
val fragment2 = TransitionFragment(R.layout.scene2)
@@ -812,7 +882,7 @@
// It does not transition properly for ordered transactions, though.
if (reorderingAllowed) {
verifyAndClearTransition(fragment1.returnTransition, null, startGreen)
- val endGreen = findGreen()
+ val endGreen = activityRule.findGreen()
verifyAndClearTransition(fragment2.enterTransition, startGreenBounds, endGreen)
assertThat(fragment2.sharedElementEnter.targets.size).isEqualTo(2)
fragment2.sharedElementEnter.clearTargets()
@@ -831,8 +901,8 @@
fun noMatchingSharedElementRetainName() {
val fragment1 = setupInitialFragment()
- val startBlue = findBlue()
- val startGreen = findGreen()
+ val startBlue = activityRule.findBlue()
+ val startGreen = activityRule.findGreen()
val startGreenBounds = getBoundsOnScreen(startGreen)
val fragment2 = TransitionFragment(R.layout.scene3)
@@ -846,9 +916,9 @@
.commit()
fragment2.waitForTransition()
- val midGreen = findGreen()
- val midBlue = findBlue()
- val midRed = findRed()
+ val midGreen = activityRule.findGreen()
+ val midBlue = activityRule.findBlue()
+ val midRed = activityRule.findRed()
val midGreenBounds = getBoundsOnScreen(midGreen)
if (reorderingAllowed) {
verifyAndClearTransition(
@@ -868,8 +938,8 @@
fragment2.waitForTransition()
fragment1.waitForTransition()
- val endBlue = findBlue()
- val endGreen = findGreen()
+ val endBlue = activityRule.findBlue()
+ val endGreen = activityRule.findGreen()
assertThat(endBlue.transitionName).isEqualTo("blueSquare")
assertThat(endGreen.transitionName).isEqualTo("greenSquare")
@@ -885,8 +955,8 @@
activityRule.waitForExecution()
assertThat(onBackStackChangedTimes).isEqualTo(1)
fragment1.waitForTransition()
- val blueSquare1 = findBlue()
- val greenSquare1 = findGreen()
+ val blueSquare1 = activityRule.findBlue()
+ val greenSquare1 = activityRule.findGreen()
verifyAndClearTransition(fragment1.enterTransition, null, blueSquare1, greenSquare1)
verifyNoOtherTransitions(fragment1)
return fragment1
@@ -896,82 +966,15 @@
return fragment.requireView().findViewById(id)
}
- private fun findGreen(): View {
- return activityRule.activity.findViewById(R.id.greenSquare)
- }
-
- private fun findBlue(): View {
- return activityRule.activity.findViewById(R.id.blueSquare)
- }
-
- private fun findRed(): View? {
- return activityRule.activity.findViewById(R.id.redSquare)
- }
-
- private fun verifyAndClearTransition(
- transition: TargetTracking,
- epicenter: Rect?,
- vararg expected: View
- ) {
- if (epicenter == null) {
- assertThat(transition.capturedEpicenter).isNull()
- } else {
- assertThat(transition.capturedEpicenter).isEqualTo(epicenter)
- }
- val targets = transition.trackedTargets
- val sb = StringBuilder()
- sb.append("Expected: [")
- .append(expected.size)
- .append("] {")
- var isFirst = true
- for (view in expected) {
- if (isFirst) {
- isFirst = false
- } else {
- sb.append(", ")
- }
- sb.append(view)
- }
- sb.append("}, but got: [")
- .append(targets.size)
- .append("] {")
- isFirst = true
- for (view in targets) {
- if (isFirst) {
- isFirst = false
- } else {
- sb.append(", ")
- }
- sb.append(view)
- }
- sb.append("}")
- val errorMessage = sb.toString()
-
- assertWithMessage(errorMessage).that(targets.size).isEqualTo(expected.size)
- for (view in expected) {
- assertWithMessage(errorMessage).that(targets.contains(view)).isTrue()
- }
- transition.clearTargets()
- }
-
- private fun verifyNoOtherTransitions(fragment: TransitionFragment) {
- assertThat(fragment.enterTransition.targets.size).isEqualTo(0)
- assertThat(fragment.exitTransition.targets.size).isEqualTo(0)
- assertThat(fragment.reenterTransition.targets.size).isEqualTo(0)
- assertThat(fragment.returnTransition.targets.size).isEqualTo(0)
- assertThat(fragment.sharedElementEnter.targets.size).isEqualTo(0)
- assertThat(fragment.sharedElementReturn.targets.size).isEqualTo(0)
- }
-
private fun verifyTransition(
from: TransitionFragment,
to: TransitionFragment,
sharedElementName: String
) {
val startOnBackStackChanged = onBackStackChangedTimes
- val startBlue = findBlue()
- val startGreen = findGreen()
- val startRed = findRed()
+ val startBlue = activityRule.findBlue()
+ val startGreen = activityRule.findGreen()
+ val startRed = activityRule.findRed()
val startBlueRect = getBoundsOnScreen(startBlue)
@@ -986,9 +989,9 @@
assertThat(onBackStackChangedTimes).isEqualTo(startOnBackStackChanged + 1)
to.waitForTransition()
- val endGreen = findGreen()
- val endBlue = findBlue()
- val endRed = findRed()
+ val endGreen = activityRule.findGreen()
+ val endBlue = activityRule.findBlue()
+ val endRed = activityRule.findRed()
val endBlueRect = getBoundsOnScreen(endBlue)
if (startRed != null) {
@@ -1113,9 +1116,9 @@
vararg others: TransitionFragment
) {
val startOnBackStackChanged = onBackStackChangedTimes
- val startBlue = findBlue()
- val startGreen = findGreen()
- val startRed = findRed()
+ val startBlue = activityRule.findBlue()
+ val startGreen = activityRule.findGreen()
+ val startRed = activityRule.findRed()
val startSharedRect = getBoundsOnScreen(startBlue)
instrumentation.runOnMainSync {
@@ -1127,9 +1130,9 @@
assertThat(onBackStackChangedTimes).isEqualTo((startOnBackStackChanged + 1))
to.waitForTransition()
- val endGreen = findGreen()
- val endBlue = findBlue()
- val endRed = findRed()
+ val endGreen = activityRule.findGreen()
+ val endBlue = activityRule.findBlue()
+ val endRed = activityRule.findRed()
val endSharedRect = getBoundsOnScreen(endBlue)
if (startRed != null) {
@@ -1182,6 +1185,14 @@
}
}
+ class TransitionFinishFirstFragment : TransitionFragment(R.layout.scene1) {
+ override fun onDestroyView() {
+ // Ensure all transitions have been executed before onDestroyView was called
+ assertThat(endTransitionCountDownLatch.count).isEqualTo(0)
+ super.onDestroyView()
+ }
+ }
+
companion object {
@JvmStatic
@Parameterized.Parameters
diff --git a/fragment/fragment/src/androidTest/java/androidx/fragment/app/PostponedTransitionTest.kt b/fragment/fragment/src/androidTest/java/androidx/fragment/app/PostponedTransitionTest.kt
index 9098d94..9ebae46 100644
--- a/fragment/fragment/src/androidTest/java/androidx/fragment/app/PostponedTransitionTest.kt
+++ b/fragment/fragment/src/androidTest/java/androidx/fragment/app/PostponedTransitionTest.kt
@@ -748,7 +748,9 @@
assertThat(fragment2.isResumed).isFalse()
assertThat(fragment2.isAdded).isFalse()
- assertThat(fragment2.view).isNull()
+ activityRule.runOnUiThread {
+ assertThat(fragment2.view).isNull()
+ }
}
// Ensure that the postponed fragment transactions don't allow reentrancy in fragment manager
@@ -849,37 +851,37 @@
// Ensure that if postponedEnterTransition is called twice the first one is removed.
@Test
fun testTimedPostponeEnterPostponedCalledTwice() {
- val fm = activityRule.activity.supportFragmentManager
- val startBlue = activityRule.activity.findViewById<View>(R.id.blueSquare)
+ val fm = activityRule.activity.supportFragmentManager
+ val startBlue = activityRule.activity.findViewById<View>(R.id.blueSquare)
- val fragment = PostponedFragment3(1000)
- fragment.startPostponedCountDownLatch = CountDownLatch(2)
- fm.beginTransaction()
- .addSharedElement(startBlue, "blueSquare")
- .replace(R.id.fragmentContainer, fragment)
- .addToBackStack(null)
- .setReorderingAllowed(true)
- .commit()
+ val fragment = PostponedFragment3(1000)
+ fragment.startPostponedCountDownLatch = CountDownLatch(2)
+ fm.beginTransaction()
+ .addSharedElement(startBlue, "blueSquare")
+ .replace(R.id.fragmentContainer, fragment)
+ .addToBackStack(null)
+ .setReorderingAllowed(true)
+ .commit()
- fragment.postponeEnterTransition(1000, TimeUnit.MILLISECONDS)
+ fragment.postponeEnterTransition(1000, TimeUnit.MILLISECONDS)
- activityRule.waitForExecution()
+ activityRule.waitForExecution()
- assertWithMessage("Fragment should be postponed")
- .that(fragment.isPostponed).isTrue()
+ assertWithMessage("Fragment should be postponed")
+ .that(fragment.isPostponed).isTrue()
- assertThat(fragment.startPostponedCountDownLatch.count).isEqualTo(2)
+ assertThat(fragment.startPostponedCountDownLatch.count).isEqualTo(2)
- assertPostponedTransition(beginningFragment, fragment)
+ assertPostponedTransition(beginningFragment, fragment)
- fragment.waitForTransition()
+ fragment.waitForTransition()
- assertWithMessage(
- "After startPostponed is called the transition should not be postponed"
- )
- .that(fragment.isPostponed).isFalse()
+ assertWithMessage(
+ "After startPostponed is called the transition should not be postponed"
+ )
+ .that(fragment.isPostponed).isFalse()
- assertThat(fragment.startPostponedCountDownLatch.count).isEqualTo(1)
+ assertThat(fragment.startPostponedCountDownLatch.count).isEqualTo(1)
}
// Ensure that if postponedEnterTransition with a duration of 0 it waits one frame.
diff --git a/fragment/fragment/src/androidTest/java/androidx/fragment/app/TransitionFragment.kt b/fragment/fragment/src/androidTest/java/androidx/fragment/app/TransitionFragment.kt
index 72dc1bb..7bc21aa 100644
--- a/fragment/fragment/src/androidTest/java/androidx/fragment/app/TransitionFragment.kt
+++ b/fragment/fragment/src/androidTest/java/androidx/fragment/app/TransitionFragment.kt
@@ -18,6 +18,7 @@
import android.transition.Transition
import androidx.annotation.LayoutRes
import androidx.fragment.test.R
+import androidx.lifecycle.Lifecycle
import com.google.common.truth.Truth.assertThat
import java.util.concurrent.CountDownLatch
import java.util.concurrent.TimeUnit
@@ -37,8 +38,10 @@
var startTransitionCountDownLatch = CountDownLatch(1)
var endTransitionCountDownLatch = CountDownLatch(1)
- private val listener = object : Transition.TransitionListener {
+ val listener = object : Transition.TransitionListener {
override fun onTransitionEnd(transition: Transition) {
+ assertThat(viewLifecycleOwner.lifecycle.currentState)
+ .isNotEqualTo(Lifecycle.State.DESTROYED)
endTransitionCountDownLatch.countDown()
startTransitionCountDownLatch = CountDownLatch(1)
}
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 ccbc082..ff0ab99 100644
--- a/fragment/fragment/src/main/java/androidx/fragment/app/BackStackRecord.java
+++ b/fragment/fragment/src/main/java/androidx/fragment/app/BackStackRecord.java
@@ -400,7 +400,7 @@
switch (op.mCmd) {
case OP_ADD:
f.setNextAnim(op.mEnterAnim);
- mManager.addFragment(f, false);
+ mManager.addFragment(f);
break;
case OP_REMOVE:
f.setNextAnim(op.mExitAnim);
@@ -465,7 +465,7 @@
break;
case OP_REMOVE:
f.setNextAnim(op.mPopEnterAnim);
- mManager.addFragment(f, false);
+ mManager.addFragment(f);
break;
case OP_HIDE:
f.setNextAnim(op.mPopEnterAnim);
diff --git a/fragment/fragment/src/main/java/androidx/fragment/app/FragmentLayoutInflaterFactory.java b/fragment/fragment/src/main/java/androidx/fragment/app/FragmentLayoutInflaterFactory.java
index 91842df..faa0829 100644
--- a/fragment/fragment/src/main/java/androidx/fragment/app/FragmentLayoutInflaterFactory.java
+++ b/fragment/fragment/src/main/java/androidx/fragment/app/FragmentLayoutInflaterFactory.java
@@ -101,7 +101,8 @@
fragment.mHost = mFragmentManager.mHost;
fragment.onInflate(mFragmentManager.mHost.getContext(), attrs,
fragment.mSavedFragmentState);
- mFragmentManager.addFragment(fragment, true);
+ mFragmentManager.addFragment(fragment);
+ mFragmentManager.moveToState(fragment);
} else if (fragment.mInLayout) {
// A fragment already exists and it is not one we restored from
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 6278bae..fefe194 100644
--- a/fragment/fragment/src/main/java/androidx/fragment/app/FragmentManager.java
+++ b/fragment/fragment/src/main/java/androidx/fragment/app/FragmentManager.java
@@ -69,6 +69,7 @@
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
+import java.util.HashSet;
import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.atomic.AtomicInteger;
@@ -187,6 +188,17 @@
}
/**
+ * Interface to indicate when exit animations are complete
+ */
+ interface ExitAnimationCompleteMarker {
+ /**
+ * Called when Animation objects need to be canceled.
+ */
+ @MainThread
+ void cancel();
+ }
+
+ /**
* Callback interface for listening to fragment state changes that happen
* within a given FragmentManager.
*/
@@ -366,15 +378,17 @@
private OnBackPressedDispatcher mOnBackPressedDispatcher;
private final OnBackPressedCallback mOnBackPressedCallback =
new OnBackPressedCallback(false) {
- @Override
- public void handleOnBackPressed() {
- FragmentManager.this.handleOnBackPressed();
- }
- };
+ @Override
+ public void handleOnBackPressed() {
+ FragmentManager.this.handleOnBackPressed();
+ }
+ };
private final AtomicInteger mBackStackIndex = new AtomicInteger();
private ArrayList<OnBackStackChangedListener> mBackStackChangeListeners;
+ private HashMap<Fragment, HashSet<ExitAnimationCompleteMarker>>
+ mExitAnimationCompleteMarkers = new HashMap<>();
private final CopyOnWriteArrayList<FragmentLifecycleCallbacksHolder>
mLifecycleCallbacks = new CopyOnWriteArrayList<>();
@@ -712,6 +726,34 @@
}
/**
+ * Add new listener for exit animation end callbacks
+ */
+ void addExitAnimationCompleteMarker(@NonNull Fragment f,
+ @NonNull ExitAnimationCompleteMarker listener) {
+ if (mExitAnimationCompleteMarkers.get(f) == null) {
+ mExitAnimationCompleteMarkers.put(f, new HashSet<ExitAnimationCompleteMarker>());
+ }
+ mExitAnimationCompleteMarkers.get(f).add(listener);
+ }
+
+ /**
+ * Remove a listener that was previously added with
+ * {@link #addExitAnimationCompleteMarker(Fragment, ExitAnimationCompleteMarker)}.
+ *
+ * Destroy the view of the Fragment associated with that listener and move it to the proper
+ * state.
+ */
+ void removeExitAnimationComplete(@NonNull Fragment f,
+ @NonNull ExitAnimationCompleteMarker marker) {
+ HashSet<ExitAnimationCompleteMarker> markers = mExitAnimationCompleteMarkers.get(f);
+ if (markers != null && markers.remove(marker) && markers.isEmpty()) {
+ destroyFragmentView(f);
+ mExitAnimationCompleteMarkers.remove(f);
+ moveToState(f, f.getStateAfterAnimating());
+ }
+ }
+
+ /**
* Put a reference to a fragment in a Bundle. This Bundle can be
* persisted as saved state, and when later restoring
* {@link #getFragment(Bundle, String)} will return the current
@@ -962,7 +1004,7 @@
* @param args Additional arguments to the dump request.
*/
public void dump(@NonNull String prefix, @Nullable FileDescriptor fd,
- @NonNull PrintWriter writer, @Nullable String[] args) {
+ @NonNull PrintWriter writer, @Nullable String[] args) {
String innerPrefix = prefix + " ";
if (!mActive.isEmpty()) {
@@ -1166,13 +1208,13 @@
if ((!f.mAdded || f.mDetached) && newState > Fragment.CREATED) {
newState = Fragment.CREATED;
}
- if (f.mRemoving && newState > f.mState) {
- if (f.mState == Fragment.INITIALIZING && f.isInBackStack()) {
- // Allow the fragment to be created so that it can be saved later.
- newState = Fragment.CREATED;
+ if (f.mRemoving) {
+ if (f.isInBackStack()) {
+ // Fragments on the back stack shouldn't go higher than CREATED
+ newState = Math.min(newState, Fragment.CREATED);
} else {
- // While removing a fragment, we can't change it to a higher state.
- newState = f.mState;
+ // While removing a fragment, we always move to INITIALIZING
+ newState = Fragment.INITIALIZING;
}
}
// Defer start if requested; don't allow it to move to STARTED or higher
@@ -1194,12 +1236,12 @@
if (f.mFromLayout && !f.mInLayout) {
return;
}
- if (f.mState < newState && (f.getAnimatingAway() != null || f.getAnimator() != null)) {
+ // If we are moving to the same state, we do not need to give up on the animation.
+ if (f.mState < newState && !mExitAnimationCompleteMarkers.isEmpty()) {
// The fragment is currently being animated... but! Now we
// want to move our state back up. Give up on waiting for the
// animation and proceed from where we are.
- f.setAnimatingAway(null);
- f.setAnimator(null);
+ cancelExitAnimation(f);
}
switch (f.mState) {
case Fragment.INITIALIZING:
@@ -1406,13 +1448,17 @@
}
f.mPostponedAlpha = 0;
if (anim != null) {
- animateRemoveFragment(f, anim, newState);
+ animateRemoveFragment(f, anim);
}
f.mContainer.removeView(f.mView);
}
}
- if (anim == null) {
+ // If a fragment has an exit animation (or transition), do not destroy
+ // its view immediately and set the state after animating
+ if (mExitAnimationCompleteMarkers.get(f) == null) {
destroyFragmentView(f);
+ } else {
+ f.setStateAfterAnimating(newState);
}
}
// fall through
@@ -1423,23 +1469,19 @@
// being destroyed, but this fragment is
// currently animating away. Stop the
// animation right now -- it is not needed,
- // and we can't wait any more on destroying
- // the fragment.
- if (f.getAnimatingAway() != null) {
- View v = f.getAnimatingAway();
- f.setAnimatingAway(null);
- v.clearAnimation();
- } else if (f.getAnimator() != null) {
- Animator animator = f.getAnimator();
- f.setAnimator(null);
- animator.cancel();
+ // and we can't wait anymore. we need to destroy
+ // the view now and cancel the animation
+ if (mExitAnimationCompleteMarkers.get(f) != null) {
+ cancelExitAnimation(f);
}
}
- if (f.getAnimatingAway() != null || f.getAnimator() != null) {
+ if (!mExitAnimationCompleteMarkers.isEmpty()) {
// We are waiting for the fragment's view to finish
// animating away. Just make a note of the state
// the fragment now should move to once the animation
// is done.
+ // Shared elements require that we wait on multiple Fragments, so if
+ // any of them are animating we will continue to wait.
f.setStateAfterAnimating(newState);
newState = Fragment.CREATED;
} else {
@@ -1500,14 +1542,25 @@
*
* @param fragment The fragment to animate out
* @param anim The animator or animation to run on the fragment's view
- * @param newState The final state after animating.
*/
private void animateRemoveFragment(@NonNull final Fragment fragment,
- @NonNull AnimationOrAnimator anim, final int newState) {
+ @NonNull AnimationOrAnimator anim) {
final View viewToAnimate = fragment.mView;
final ViewGroup container = fragment.mContainer;
container.startViewTransition(viewToAnimate);
- fragment.setStateAfterAnimating(newState);
+ final ExitAnimationCompleteMarker marker =
+ new FragmentManager.ExitAnimationCompleteMarker() {
+ @Override
+ public void cancel() {
+ if (fragment.getAnimatingAway() != null) {
+ View v = fragment.getAnimatingAway();
+ fragment.setAnimatingAway(null);
+ v.clearAnimation();
+ }
+ fragment.setAnimator(null);
+ }
+ };
+ addExitAnimationCompleteMarker(fragment, marker);
if (anim.animation != null) {
Animation animation =
new EndViewTransitionAnimation(anim.animation, container, viewToAnimate);
@@ -1527,8 +1580,7 @@
public void run() {
if (fragment.getAnimatingAway() != null) {
fragment.setAnimatingAway(null);
- destroyFragmentView(fragment);
- moveToState(fragment, fragment.getStateAfterAnimating());
+ removeExitAnimationComplete(fragment, marker);
}
}
});
@@ -1551,8 +1603,7 @@
Animator animator = fragment.getAnimator();
fragment.setAnimator(null);
if (animator != null && container.indexOfChild(viewToAnimate) < 0) {
- destroyFragmentView(fragment);
- moveToState(fragment, fragment.getStateAfterAnimating());
+ removeExitAnimationComplete(fragment, marker);
}
}
});
@@ -1561,7 +1612,23 @@
}
}
- void destroyFragmentView(@NonNull Fragment fragment) {
+ // If there is a listener associated with the given fragment, remove that listener and
+ // destroy the fragment's view.
+ private void cancelExitAnimation(@NonNull Fragment f) {
+ HashSet<ExitAnimationCompleteMarker> markers = mExitAnimationCompleteMarkers.get(f);
+ if (markers != null) {
+ for (ExitAnimationCompleteMarker marker: markers) {
+ marker.cancel();
+ markers.remove(marker);
+ if (markers.isEmpty()) {
+ destroyFragmentView(f);
+ mExitAnimationCompleteMarkers.remove(f);
+ }
+ }
+ }
+ }
+
+ private void destroyFragmentView(@NonNull Fragment fragment) {
fragment.performDestroyView();
dispatchOnFragmentViewDestroyed(fragment, false);
fragment.mContainer = null;
@@ -1671,15 +1738,7 @@
}
return;
}
- int nextState = mCurState;
- if (f.mRemoving) {
- if (f.isInBackStack()) {
- nextState = Math.min(nextState, Fragment.CREATED);
- } else {
- nextState = Math.min(nextState, Fragment.INITIALIZING);
- }
- }
- moveToState(f, nextState);
+ moveToState(f);
if (f.mView != null) {
// Move the view if it is out of order
@@ -1814,7 +1873,7 @@
f.initState();
}
- void addFragment(Fragment fragment, boolean moveToStateNow) {
+ void addFragment(Fragment fragment) {
if (DEBUG) Log.v(TAG, "add: " + fragment);
makeActive(fragment);
if (!fragment.mDetached) {
@@ -1832,9 +1891,6 @@
if (isMenuAvailable(fragment)) {
mNeedMenuInvalidate = true;
}
- if (moveToStateNow) {
- moveToState(fragment);
- }
}
}
@@ -2408,7 +2464,7 @@
*/
@SuppressWarnings("WeakerAccess") /* synthetic access */
void completeExecute(BackStackRecord record, boolean isPop, boolean runTransitions,
- boolean moveToState) {
+ boolean moveToState) {
if (isPop) {
record.executePopOps(moveToState);
} else {
@@ -2545,24 +2601,10 @@
*/
private void endAnimatingAwayFragments() {
for (Fragment fragment : mActive.values()) {
- if (fragment != null) {
- if (fragment.getAnimatingAway() != null) {
- // Give up waiting for the animation and just end it.
- final int stateAfterAnimating = fragment.getStateAfterAnimating();
- final View animatingAway = fragment.getAnimatingAway();
- Animation animation = animatingAway.getAnimation();
- if (animation != null) {
- animation.cancel();
- // force-clear the animation, as Animation#cancel() doesn't work prior to N,
- // and will instead cause the animation to infinitely loop
- animatingAway.clearAnimation();
- }
- fragment.setAnimatingAway(null);
- destroyFragmentView(fragment);
- moveToState(fragment, stateAfterAnimating);
- } else if (fragment.getAnimator() != null) {
- fragment.getAnimator().end();
- }
+ if (fragment != null && mExitAnimationCompleteMarkers.get(fragment) != null) {
+ // Give up waiting for the animation and just end it.
+ cancelExitAnimation(fragment);
+ moveToState(fragment, fragment.getStateAfterAnimating());
}
}
}
diff --git a/fragment/fragment/src/main/java/androidx/fragment/app/FragmentTransition.java b/fragment/fragment/src/main/java/androidx/fragment/app/FragmentTransition.java
index ddd481d..b8e993a 100644
--- a/fragment/fragment/src/main/java/androidx/fragment/app/FragmentTransition.java
+++ b/fragment/fragment/src/main/java/androidx/fragment/app/FragmentTransition.java
@@ -17,6 +17,7 @@
import android.graphics.Rect;
import android.os.Build;
+import android.transition.Transition;
import android.util.SparseArray;
import android.view.View;
import android.view.ViewGroup;
@@ -252,6 +253,11 @@
Object transition = mergeTransitions(impl, enterTransition, exitTransition,
sharedElementTransition, inFragment, inIsPop);
+ if (outFragment != null && exitingViews != null
+ && (exitingViews.size() > 0 || sharedElementsOut.size() > 0)) {
+ setListenerForTransitionEnd(fragmentManager, outFragment, transition);
+ }
+
if (transition != null) {
replaceHide(impl, exitTransition, outFragment, exitingViews);
ArrayList<String> inNames =
@@ -269,6 +275,60 @@
}
/**
+ * Set a listener for Transition end events.
+ *
+ * If either exitingViews or SharedElementsOut contain a view, a
+ * {@link FragmentManager.ExitAnimationCompleteMarker} will added to the
+ * given FragmentManager. The FragmentManager will check for the
+ * {@link FragmentManager.ExitAnimationCompleteMarker} to determine if the transition has
+ * finished.
+ *
+ * An {@link Transition.TransitionListener#onTransitionEnd} listener is added that removes
+ * the {@link FragmentManager.ExitAnimationCompleteMarker} from the given FragmentManager and
+ * that indicates to the FragmentManager that it should properly handle the out going Fragment.
+ *
+ * If the FragmentManager wishes to cancel the transition, it removes the
+ * {@link FragmentManager.ExitAnimationCompleteMarker} and destroys the view of the Fragment.
+ *
+ * @param fragmentManager The executing FragmentManager
+ * @param outFragment The first fragment that is exiting
+ * @param transition all transitions to be executed on a single container
+ */
+ private static void setListenerForTransitionEnd(final FragmentManager fragmentManager,
+ final Fragment outFragment, Object transition) {
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
+ final FragmentManager.ExitAnimationCompleteMarker marker =
+ new FragmentManager.ExitAnimationCompleteMarker() {
+ @Override
+ // Transitions do not need to do anything on cancel
+ public void cancel() { }
+ };
+ fragmentManager.addExitAnimationCompleteMarker(outFragment, marker);
+ ((Transition) transition).addListener(new Transition.TransitionListener() {
+ @Override
+ public void onTransitionStart(Transition transition) { }
+
+ @Override
+ public void onTransitionEnd(Transition transition) {
+ if (outFragment.mView != null && outFragment.mState <= Fragment.CREATED
+ && (outFragment.mRemoving || outFragment.isDetached())) {
+ fragmentManager.removeExitAnimationComplete(outFragment, marker);
+ }
+ }
+
+ @Override
+ public void onTransitionCancel(Transition transition) { }
+
+ @Override
+ public void onTransitionPause(Transition transition) { }
+
+ @Override
+ public void onTransitionResume(Transition transition) { }
+ });
+ }
+ }
+
+ /**
* Replace hide operations with visibility changes on the exiting views. Instead of making
* the entire fragment's view GONE, make each exiting view INVISIBLE. At the end of the
* transition, make the fragment's view GONE.
@@ -354,6 +414,11 @@
Object transition = mergeTransitions(impl, enterTransition, exitTransition,
sharedElementTransition, inFragment, fragments.lastInIsPop);
+ if (outFragment != null && exitingViews != null
+ && (exitingViews.size() > 0 || sharedElementsOut.size() > 0)) {
+ setListenerForTransitionEnd(fragmentManager, outFragment, transition);
+ }
+
if (transition != null) {
final ArrayList<View> enteringViews = new ArrayList<>();
impl.scheduleRemoveTargets(transition,
diff --git a/graphics/drawable/static/build.gradle b/graphics/drawable/static/build.gradle
index a3e453a..f5e1ba4 100644
--- a/graphics/drawable/static/build.gradle
+++ b/graphics/drawable/static/build.gradle
@@ -10,7 +10,7 @@
dependencies {
api("androidx.annotation:annotation:1.1.0")
- api("androidx.core:core:1.1.0-rc01")
+ api("androidx.core:core:1.1.0")
implementation("androidx.collection:collection:1.1.0")
androidTestImplementation(ANDROIDX_TEST_EXT_JUNIT)
diff --git a/gridlayout/build.gradle b/gridlayout/build.gradle
index efb7375..58ab795 100644
--- a/gridlayout/build.gradle
+++ b/gridlayout/build.gradle
@@ -10,7 +10,7 @@
dependencies {
api(ANDROIDX_ANNOTATION)
- implementation("androidx.core:core:1.1.0-rc01")
+ implementation("androidx.core:core:1.1.0")
androidTestImplementation(ANDROIDX_TEST_EXT_JUNIT)
androidTestImplementation(ANDROIDX_TEST_CORE)
diff --git a/inspection/inspection/src/main/java/androidx/inspection/Connection.java b/inspection/inspection/src/main/java/androidx/inspection/Connection.java
new file mode 100644
index 0000000..fd0c3f2
--- /dev/null
+++ b/inspection/inspection/src/main/java/androidx/inspection/Connection.java
@@ -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.inspection;
+
+import androidx.annotation.NonNull;
+
+/**
+ * A class representing a connection between studio and inspectors.
+ */
+public abstract class Connection {
+
+ /**
+ * Sends raw bytes to studio.
+ *
+ * @param data An array of bytes. Up to inspectors to determine how to encode bytes.
+ */
+ public void sendEvent(@NonNull byte[] data) {
+ }
+}
diff --git a/inspection/inspection/src/main/java/androidx/inspection/Inspector.java b/inspection/inspection/src/main/java/androidx/inspection/Inspector.java
index c7686ad7..d5b68fc 100644
--- a/inspection/inspection/src/main/java/androidx/inspection/Inspector.java
+++ b/inspection/inspection/src/main/java/androidx/inspection/Inspector.java
@@ -16,16 +16,29 @@
package androidx.inspection;
+import androidx.annotation.NonNull;
+
/**
- * Implementation of this class are responsible to handle command from frontend and
- * send back events.
+ * Implementation of this class are responsible to handle command from frontend and
+ * send back events.
*/
public abstract class Inspector {
+
+ public Inspector(@NonNull Connection connection) {
+ }
+
/**
- * Called when this inspector was disposed and no longer needed.
- * <p>
+ * Called when this inspector is no longer needed.
+ *
* Agent should use this callback to unsubscribe from any events that it is listening to.
*/
public void onDispose() {
}
+
+ /**
+ * An inspector can implement this to handle incoming commands.
+ *
+ * @param data a raw byte array of the command sent by studio.
+ */
+ public abstract void onReceiveCommand(@NonNull byte[] data);
}
diff --git a/inspection/inspection/src/main/java/androidx/inspection/InspectorFactory.java b/inspection/inspection/src/main/java/androidx/inspection/InspectorFactory.java
index f0e3568..082f223 100644
--- a/inspection/inspection/src/main/java/androidx/inspection/InspectorFactory.java
+++ b/inspection/inspection/src/main/java/androidx/inspection/InspectorFactory.java
@@ -47,10 +47,12 @@
return mInspectorId;
}
- // TODO: pass the connection in this method
/**
+ * Creates a new inspector with the provided connection.
+ *
+ * @param connection a connection to send events.
* @return a new instance of an inspector.
*/
@NonNull
- public abstract T createInspector();
+ public abstract T createInspector(@NonNull Connection connection);
}
diff --git a/inspection/inspection/src/test/java/androidx/inspection/InspectorTest.kt b/inspection/inspection/src/test/java/androidx/inspection/InspectorTest.kt
index a19ad5b..b519102 100644
--- a/inspection/inspection/src/test/java/androidx/inspection/InspectorTest.kt
+++ b/inspection/inspection/src/test/java/androidx/inspection/InspectorTest.kt
@@ -26,7 +26,11 @@
@Test
fun dummyTest() {
- val value = object : Inspector() {}
+ val connection = object : Connection() {}
+ val value = object : Inspector(connection) {
+ override fun onReceiveCommand(data: ByteArray) {
+ }
+ }
assertThat(value).isNotNull()
}
}
\ No newline at end of file
diff --git a/leanback-preference/build.gradle b/leanback-preference/build.gradle
index d91fa03..e4b9361 100644
--- a/leanback-preference/build.gradle
+++ b/leanback-preference/build.gradle
@@ -12,7 +12,7 @@
implementation("androidx.collection:collection:1.0.0")
api("androidx.appcompat:appcompat:1.0.0")
api("androidx.recyclerview:recyclerview:1.0.0")
- api(project(":preference"))
+ api(project(":preference:preference"))
api(project(":leanback"))
}
diff --git a/leanback/build.gradle b/leanback/build.gradle
index 7588656..df32fcf 100644
--- a/leanback/build.gradle
+++ b/leanback/build.gradle
@@ -11,7 +11,7 @@
dependencies {
api("androidx.annotation:annotation:1.1.0")
api("androidx.interpolator:interpolator:1.0.0")
- api("androidx.core:core:1.1.0-rc01")
+ api("androidx.core:core:1.1.0")
implementation("androidx.collection:collection:1.0.0")
api("androidx.media:media:1.0.0")
api("androidx.fragment:fragment:1.0.0")
diff --git a/legacy/core-utils/build.gradle b/legacy/core-utils/build.gradle
index 7a331a9..c4fed24 100644
--- a/legacy/core-utils/build.gradle
+++ b/legacy/core-utils/build.gradle
@@ -9,7 +9,7 @@
dependencies {
api("androidx.annotation:annotation:1.1.0")
- api("androidx.core:core:1.1.0-rc01")
+ api("androidx.core:core:1.1.0")
api(project(":documentfile"))
api(project(":loader:loader"))
api(project(":localbroadcastmanager"))
diff --git a/lifecycle/lifecycle-livedata-ktx/api/2.2.0-alpha04.txt b/lifecycle/lifecycle-livedata-ktx/api/2.2.0-alpha04.txt
index 26f3e49..c8887ad 100644
--- a/lifecycle/lifecycle-livedata-ktx/api/2.2.0-alpha04.txt
+++ b/lifecycle/lifecycle-livedata-ktx/api/2.2.0-alpha04.txt
@@ -1,25 +1,19 @@
// Signature format: 3.0
package androidx.lifecycle {
- public final class CoroutineLiveDataApi26Kt {
- ctor public CoroutineLiveDataApi26Kt();
- method @RequiresApi(android.os.Build.VERSION_CODES.O) public static <T> androidx.lifecycle.LiveData<T> liveData(kotlin.coroutines.CoroutineContext context = EmptyCoroutineContext, java.time.Duration timeout, kotlin.jvm.functions.Function2<? super androidx.lifecycle.LiveDataScope<T>,? super kotlin.coroutines.Continuation<? super kotlin.Unit>,?> block);
- }
-
public final class CoroutineLiveDataKt {
ctor public CoroutineLiveDataKt();
method public static <T> androidx.lifecycle.LiveData<T> liveData(kotlin.coroutines.CoroutineContext context = EmptyCoroutineContext, long timeoutInMs = 5000L, kotlin.jvm.functions.Function2<? super androidx.lifecycle.LiveDataScope<T>,? super kotlin.coroutines.Continuation<? super kotlin.Unit>,?> block);
+ method @RequiresApi(android.os.Build.VERSION_CODES.O) public static <T> androidx.lifecycle.LiveData<T> liveData(kotlin.coroutines.CoroutineContext context = EmptyCoroutineContext, java.time.Duration timeout, kotlin.jvm.functions.Function2<? super androidx.lifecycle.LiveDataScope<T>,? super kotlin.coroutines.Continuation<? super kotlin.Unit>,?> block);
}
- public final class FlowLiveDataApi26Kt {
- ctor public FlowLiveDataApi26Kt();
- method @RequiresApi(android.os.Build.VERSION_CODES.O) public static <T> androidx.lifecycle.LiveData<T> asLiveData(kotlinx.coroutines.flow.Flow<? extends T>, kotlin.coroutines.CoroutineContext context = EmptyCoroutineContext, java.time.Duration timeout);
- }
-
- public final class FlowLiveDataKt {
- ctor public FlowLiveDataKt();
+ public final class FlowLiveDataConversions {
+ ctor public FlowLiveDataConversions();
method public static <T> kotlinx.coroutines.flow.Flow<T> asFlow(androidx.lifecycle.LiveData<T>);
method public static <T> androidx.lifecycle.LiveData<T> asLiveData(kotlinx.coroutines.flow.Flow<? extends T>, kotlin.coroutines.CoroutineContext context = EmptyCoroutineContext, long timeoutInMs = 5000L);
+ method public static <T> androidx.lifecycle.LiveData<T> asLiveData(kotlinx.coroutines.flow.Flow<? extends T>, kotlin.coroutines.CoroutineContext context = EmptyCoroutineContext);
+ method public static <T> androidx.lifecycle.LiveData<T> asLiveData(kotlinx.coroutines.flow.Flow<? extends T>);
+ method @RequiresApi(android.os.Build.VERSION_CODES.O) public static <T> androidx.lifecycle.LiveData<T> asLiveData(kotlinx.coroutines.flow.Flow<? extends T>, kotlin.coroutines.CoroutineContext context = EmptyCoroutineContext, java.time.Duration timeout);
}
public interface LiveDataScope<T> {
diff --git a/lifecycle/lifecycle-livedata-ktx/api/current.txt b/lifecycle/lifecycle-livedata-ktx/api/current.txt
index 26f3e49..c8887ad 100644
--- a/lifecycle/lifecycle-livedata-ktx/api/current.txt
+++ b/lifecycle/lifecycle-livedata-ktx/api/current.txt
@@ -1,25 +1,19 @@
// Signature format: 3.0
package androidx.lifecycle {
- public final class CoroutineLiveDataApi26Kt {
- ctor public CoroutineLiveDataApi26Kt();
- method @RequiresApi(android.os.Build.VERSION_CODES.O) public static <T> androidx.lifecycle.LiveData<T> liveData(kotlin.coroutines.CoroutineContext context = EmptyCoroutineContext, java.time.Duration timeout, kotlin.jvm.functions.Function2<? super androidx.lifecycle.LiveDataScope<T>,? super kotlin.coroutines.Continuation<? super kotlin.Unit>,?> block);
- }
-
public final class CoroutineLiveDataKt {
ctor public CoroutineLiveDataKt();
method public static <T> androidx.lifecycle.LiveData<T> liveData(kotlin.coroutines.CoroutineContext context = EmptyCoroutineContext, long timeoutInMs = 5000L, kotlin.jvm.functions.Function2<? super androidx.lifecycle.LiveDataScope<T>,? super kotlin.coroutines.Continuation<? super kotlin.Unit>,?> block);
+ method @RequiresApi(android.os.Build.VERSION_CODES.O) public static <T> androidx.lifecycle.LiveData<T> liveData(kotlin.coroutines.CoroutineContext context = EmptyCoroutineContext, java.time.Duration timeout, kotlin.jvm.functions.Function2<? super androidx.lifecycle.LiveDataScope<T>,? super kotlin.coroutines.Continuation<? super kotlin.Unit>,?> block);
}
- public final class FlowLiveDataApi26Kt {
- ctor public FlowLiveDataApi26Kt();
- method @RequiresApi(android.os.Build.VERSION_CODES.O) public static <T> androidx.lifecycle.LiveData<T> asLiveData(kotlinx.coroutines.flow.Flow<? extends T>, kotlin.coroutines.CoroutineContext context = EmptyCoroutineContext, java.time.Duration timeout);
- }
-
- public final class FlowLiveDataKt {
- ctor public FlowLiveDataKt();
+ public final class FlowLiveDataConversions {
+ ctor public FlowLiveDataConversions();
method public static <T> kotlinx.coroutines.flow.Flow<T> asFlow(androidx.lifecycle.LiveData<T>);
method public static <T> androidx.lifecycle.LiveData<T> asLiveData(kotlinx.coroutines.flow.Flow<? extends T>, kotlin.coroutines.CoroutineContext context = EmptyCoroutineContext, long timeoutInMs = 5000L);
+ method public static <T> androidx.lifecycle.LiveData<T> asLiveData(kotlinx.coroutines.flow.Flow<? extends T>, kotlin.coroutines.CoroutineContext context = EmptyCoroutineContext);
+ method public static <T> androidx.lifecycle.LiveData<T> asLiveData(kotlinx.coroutines.flow.Flow<? extends T>);
+ method @RequiresApi(android.os.Build.VERSION_CODES.O) public static <T> androidx.lifecycle.LiveData<T> asLiveData(kotlinx.coroutines.flow.Flow<? extends T>, kotlin.coroutines.CoroutineContext context = EmptyCoroutineContext, java.time.Duration timeout);
}
public interface LiveDataScope<T> {
diff --git a/lifecycle/lifecycle-livedata-ktx/api/restricted_2.2.0-alpha04.txt b/lifecycle/lifecycle-livedata-ktx/api/restricted_2.2.0-alpha04.txt
index 26f3e49..c8887ad 100644
--- a/lifecycle/lifecycle-livedata-ktx/api/restricted_2.2.0-alpha04.txt
+++ b/lifecycle/lifecycle-livedata-ktx/api/restricted_2.2.0-alpha04.txt
@@ -1,25 +1,19 @@
// Signature format: 3.0
package androidx.lifecycle {
- public final class CoroutineLiveDataApi26Kt {
- ctor public CoroutineLiveDataApi26Kt();
- method @RequiresApi(android.os.Build.VERSION_CODES.O) public static <T> androidx.lifecycle.LiveData<T> liveData(kotlin.coroutines.CoroutineContext context = EmptyCoroutineContext, java.time.Duration timeout, kotlin.jvm.functions.Function2<? super androidx.lifecycle.LiveDataScope<T>,? super kotlin.coroutines.Continuation<? super kotlin.Unit>,?> block);
- }
-
public final class CoroutineLiveDataKt {
ctor public CoroutineLiveDataKt();
method public static <T> androidx.lifecycle.LiveData<T> liveData(kotlin.coroutines.CoroutineContext context = EmptyCoroutineContext, long timeoutInMs = 5000L, kotlin.jvm.functions.Function2<? super androidx.lifecycle.LiveDataScope<T>,? super kotlin.coroutines.Continuation<? super kotlin.Unit>,?> block);
+ method @RequiresApi(android.os.Build.VERSION_CODES.O) public static <T> androidx.lifecycle.LiveData<T> liveData(kotlin.coroutines.CoroutineContext context = EmptyCoroutineContext, java.time.Duration timeout, kotlin.jvm.functions.Function2<? super androidx.lifecycle.LiveDataScope<T>,? super kotlin.coroutines.Continuation<? super kotlin.Unit>,?> block);
}
- public final class FlowLiveDataApi26Kt {
- ctor public FlowLiveDataApi26Kt();
- method @RequiresApi(android.os.Build.VERSION_CODES.O) public static <T> androidx.lifecycle.LiveData<T> asLiveData(kotlinx.coroutines.flow.Flow<? extends T>, kotlin.coroutines.CoroutineContext context = EmptyCoroutineContext, java.time.Duration timeout);
- }
-
- public final class FlowLiveDataKt {
- ctor public FlowLiveDataKt();
+ public final class FlowLiveDataConversions {
+ ctor public FlowLiveDataConversions();
method public static <T> kotlinx.coroutines.flow.Flow<T> asFlow(androidx.lifecycle.LiveData<T>);
method public static <T> androidx.lifecycle.LiveData<T> asLiveData(kotlinx.coroutines.flow.Flow<? extends T>, kotlin.coroutines.CoroutineContext context = EmptyCoroutineContext, long timeoutInMs = 5000L);
+ method public static <T> androidx.lifecycle.LiveData<T> asLiveData(kotlinx.coroutines.flow.Flow<? extends T>, kotlin.coroutines.CoroutineContext context = EmptyCoroutineContext);
+ method public static <T> androidx.lifecycle.LiveData<T> asLiveData(kotlinx.coroutines.flow.Flow<? extends T>);
+ method @RequiresApi(android.os.Build.VERSION_CODES.O) public static <T> androidx.lifecycle.LiveData<T> asLiveData(kotlinx.coroutines.flow.Flow<? extends T>, kotlin.coroutines.CoroutineContext context = EmptyCoroutineContext, java.time.Duration timeout);
}
public interface LiveDataScope<T> {
diff --git a/lifecycle/lifecycle-livedata-ktx/api/restricted_current.txt b/lifecycle/lifecycle-livedata-ktx/api/restricted_current.txt
index 26f3e49..c8887ad 100644
--- a/lifecycle/lifecycle-livedata-ktx/api/restricted_current.txt
+++ b/lifecycle/lifecycle-livedata-ktx/api/restricted_current.txt
@@ -1,25 +1,19 @@
// Signature format: 3.0
package androidx.lifecycle {
- public final class CoroutineLiveDataApi26Kt {
- ctor public CoroutineLiveDataApi26Kt();
- method @RequiresApi(android.os.Build.VERSION_CODES.O) public static <T> androidx.lifecycle.LiveData<T> liveData(kotlin.coroutines.CoroutineContext context = EmptyCoroutineContext, java.time.Duration timeout, kotlin.jvm.functions.Function2<? super androidx.lifecycle.LiveDataScope<T>,? super kotlin.coroutines.Continuation<? super kotlin.Unit>,?> block);
- }
-
public final class CoroutineLiveDataKt {
ctor public CoroutineLiveDataKt();
method public static <T> androidx.lifecycle.LiveData<T> liveData(kotlin.coroutines.CoroutineContext context = EmptyCoroutineContext, long timeoutInMs = 5000L, kotlin.jvm.functions.Function2<? super androidx.lifecycle.LiveDataScope<T>,? super kotlin.coroutines.Continuation<? super kotlin.Unit>,?> block);
+ method @RequiresApi(android.os.Build.VERSION_CODES.O) public static <T> androidx.lifecycle.LiveData<T> liveData(kotlin.coroutines.CoroutineContext context = EmptyCoroutineContext, java.time.Duration timeout, kotlin.jvm.functions.Function2<? super androidx.lifecycle.LiveDataScope<T>,? super kotlin.coroutines.Continuation<? super kotlin.Unit>,?> block);
}
- public final class FlowLiveDataApi26Kt {
- ctor public FlowLiveDataApi26Kt();
- method @RequiresApi(android.os.Build.VERSION_CODES.O) public static <T> androidx.lifecycle.LiveData<T> asLiveData(kotlinx.coroutines.flow.Flow<? extends T>, kotlin.coroutines.CoroutineContext context = EmptyCoroutineContext, java.time.Duration timeout);
- }
-
- public final class FlowLiveDataKt {
- ctor public FlowLiveDataKt();
+ public final class FlowLiveDataConversions {
+ ctor public FlowLiveDataConversions();
method public static <T> kotlinx.coroutines.flow.Flow<T> asFlow(androidx.lifecycle.LiveData<T>);
method public static <T> androidx.lifecycle.LiveData<T> asLiveData(kotlinx.coroutines.flow.Flow<? extends T>, kotlin.coroutines.CoroutineContext context = EmptyCoroutineContext, long timeoutInMs = 5000L);
+ method public static <T> androidx.lifecycle.LiveData<T> asLiveData(kotlinx.coroutines.flow.Flow<? extends T>, kotlin.coroutines.CoroutineContext context = EmptyCoroutineContext);
+ method public static <T> androidx.lifecycle.LiveData<T> asLiveData(kotlinx.coroutines.flow.Flow<? extends T>);
+ method @RequiresApi(android.os.Build.VERSION_CODES.O) public static <T> androidx.lifecycle.LiveData<T> asLiveData(kotlinx.coroutines.flow.Flow<? extends T>, kotlin.coroutines.CoroutineContext context = EmptyCoroutineContext, java.time.Duration timeout);
}
public interface LiveDataScope<T> {
diff --git a/lifecycle/lifecycle-livedata-ktx/src/main/java/androidx/lifecycle/CoroutineLiveData.kt b/lifecycle/lifecycle-livedata-ktx/src/main/java/androidx/lifecycle/CoroutineLiveData.kt
index f6e7ea8..c0841ac 100644
--- a/lifecycle/lifecycle-livedata-ktx/src/main/java/androidx/lifecycle/CoroutineLiveData.kt
+++ b/lifecycle/lifecycle-livedata-ktx/src/main/java/androidx/lifecycle/CoroutineLiveData.kt
@@ -16,7 +16,9 @@
package androidx.lifecycle
+import android.os.Build
import androidx.annotation.MainThread
+import androidx.annotation.RequiresApi
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.DisposableHandle
@@ -25,6 +27,7 @@
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
+import java.time.Duration
import java.util.concurrent.atomic.AtomicBoolean
import kotlin.coroutines.CoroutineContext
import kotlin.coroutines.EmptyCoroutineContext
@@ -324,4 +327,109 @@
context: CoroutineContext = EmptyCoroutineContext,
timeoutInMs: Long = DEFAULT_TIMEOUT,
@BuilderInference block: suspend LiveDataScope<T>.() -> Unit
-): LiveData<T> = CoroutineLiveData(context, timeoutInMs, block)
\ No newline at end of file
+): LiveData<T> = CoroutineLiveData(context, timeoutInMs, block)
+
+/**
+ * Builds a LiveData that has values yielded from the given [block] that executes on a
+ * [LiveDataScope].
+ *
+ * The [block] starts executing when the returned [LiveData] becomes active ([LiveData.onActive]).
+ * If the [LiveData] becomes inactive ([LiveData.onInactive]) while the [block] is executing, it
+ * will be cancelled after the [timeout] duration unless the [LiveData] becomes active again
+ * before that timeout (to gracefully handle cases like Activity rotation). Any value
+ * [LiveDataScope.emit]ed from a cancelled [block] will be ignored.
+ *
+ * After a cancellation, if the [LiveData] becomes active again, the [block] will be re-executed
+ * from the beginning. If you would like to continue the operations based on where it was stopped
+ * last, you can use the [LiveDataScope.latestValue] function to get the last
+ * [LiveDataScope.emit]ed value.
+
+ * If the [block] completes successfully *or* is cancelled due to reasons other than [LiveData]
+ * becoming inactive, it *will not* be re-executed even after [LiveData] goes through active
+ * inactive cycle.
+ *
+ * As a best practice, it is important for the [block] to cooperate in cancellation. See kotlin
+ * coroutines documentation for details
+ * https://kotlinlang.org/docs/reference/coroutines/cancellation-and-timeouts.html.
+ *
+ * ```
+ * // a simple LiveData that receives value 3, 3 seconds after being observed for the first time.
+ * val data : LiveData<Int> = liveData {
+ * delay(3000)
+ * emit(3)
+ * }
+ *
+ *
+ * // a LiveData that fetches a `User` object based on a `userId` and refreshes it every 30 seconds
+ * // as long as it is observed
+ * val userId : LiveData<String> = ...
+ * val user = userId.switchMap { id ->
+ * liveData {
+ * while(true) {
+ * // note that `while(true)` is fine because the `delay(30_000)` below will cooperate in
+ * // cancellation if LiveData is not actively observed anymore
+ * val data = api.fetch(id) // errors are ignored for brevity
+ * emit(data)
+ * delay(30_000)
+ * }
+ * }
+ * }
+ *
+ * // A retrying data fetcher with doubling back-off
+ * val user = liveData {
+ * var backOffTime = 1_000
+ * var succeeded = false
+ * while(!succeeded) {
+ * try {
+ * emit(api.fetch(id))
+ * succeeded = true
+ * } catch(ioError : IOException) {
+ * delay(backOffTime)
+ * backOffTime *= minOf(backOffTime * 2, 60_000)
+ * }
+ * }
+ * }
+ *
+ * // a LiveData that tries to load the `User` from local cache first and then tries to fetch
+ * // from the server and also yields the updated value
+ * val user = liveData {
+ * // dispatch loading first
+ * emit(LOADING(id))
+ * // check local storage
+ * val cached = cache.loadUser(id)
+ * if (cached != null) {
+ * emit(cached)
+ * }
+ * if (cached == null || cached.isStale()) {
+ * val fresh = api.fetch(id) // errors are ignored for brevity
+ * cache.save(fresh)
+ * emit(fresh)
+ * }
+ * }
+ *
+ * // a LiveData that immediately receives a LiveData<User> from the database and yields it as a
+ * // source but also tries to back-fill the database from the server
+ * val user = liveData {
+ * val fromDb: LiveData<User> = roomDatabase.loadUser(id)
+ * emitSource(fromDb)
+ * val updated = api.fetch(id) // errors are ignored for brevity
+ * // Since we are using Room here, updating the database will update the `fromDb` LiveData
+ * // that was obtained above. See Room's documentation for more details.
+ * // https://developer.android.com/training/data-storage/room/accessing-data#query-observable
+ * roomDatabase.insert(updated)
+ * }
+ * ```
+ *
+ * * @param context The CoroutineContext to run the given block in. Defaults to
+ * [EmptyCoroutineContext] combined with [Dispatchers.Main].
+ * @param timeout The timeout duration before cancelling the block if there are no active observers
+ * ([LiveData.hasActiveObservers].
+ * @param block The block to run when the [LiveData] has active observers.
+ */
+@RequiresApi(Build.VERSION_CODES.O)
+@UseExperimental(ExperimentalTypeInference::class)
+fun <T> liveData(
+ context: CoroutineContext = EmptyCoroutineContext,
+ timeout: Duration,
+ @BuilderInference block: suspend LiveDataScope<T>.() -> Unit
+): LiveData<T> = CoroutineLiveData(context, timeout.toMillis(), block)
diff --git a/lifecycle/lifecycle-livedata-ktx/src/main/java/androidx/lifecycle/CoroutineLiveDataApi26.kt b/lifecycle/lifecycle-livedata-ktx/src/main/java/androidx/lifecycle/CoroutineLiveDataApi26.kt
deleted file mode 100644
index 57b7756..0000000
--- a/lifecycle/lifecycle-livedata-ktx/src/main/java/androidx/lifecycle/CoroutineLiveDataApi26.kt
+++ /dev/null
@@ -1,132 +0,0 @@
-/*
- * 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.lifecycle
-
-import android.os.Build
-import androidx.annotation.RequiresApi
-import kotlinx.coroutines.Dispatchers
-import java.time.Duration
-import kotlin.coroutines.CoroutineContext
-import kotlin.coroutines.EmptyCoroutineContext
-import kotlin.experimental.ExperimentalTypeInference
-
-// this function resides in a separate file to avoid parsing API 26 parameter (Duration) in older
-// versions of the platform
-/**
- * Builds a LiveData that has values yielded from the given [block] that executes on a
- * [LiveDataScope].
- *
- * The [block] starts executing when the returned [LiveData] becomes active ([LiveData.onActive]).
- * If the [LiveData] becomes inactive ([LiveData.onInactive]) while the [block] is executing, it
- * will be cancelled after the [timeout] duration unless the [LiveData] becomes active again
- * before that timeout (to gracefully handle cases like Activity rotation). Any value
- * [LiveDataScope.emit]ed from a cancelled [block] will be ignored.
- *
- * After a cancellation, if the [LiveData] becomes active again, the [block] will be re-executed
- * from the beginning. If you would like to continue the operations based on where it was stopped
- * last, you can use the [LiveDataScope.latestValue] function to get the last
- * [LiveDataScope.emit]ed value.
-
- * If the [block] completes successfully *or* is cancelled due to reasons other than [LiveData]
- * becoming inactive, it *will not* be re-executed even after [LiveData] goes through active
- * inactive cycle.
- *
- * As a best practice, it is important for the [block] to cooperate in cancellation. See kotlin
- * coroutines documentation for details
- * https://kotlinlang.org/docs/reference/coroutines/cancellation-and-timeouts.html.
- *
- * ```
- * // a simple LiveData that receives value 3, 3 seconds after being observed for the first time.
- * val data : LiveData<Int> = liveData {
- * delay(3000)
- * emit(3)
- * }
- *
- *
- * // a LiveData that fetches a `User` object based on a `userId` and refreshes it every 30 seconds
- * // as long as it is observed
- * val userId : LiveData<String> = ...
- * val user = userId.switchMap { id ->
- * liveData {
- * while(true) {
- * // note that `while(true)` is fine because the `delay(30_000)` below will cooperate in
- * // cancellation if LiveData is not actively observed anymore
- * val data = api.fetch(id) // errors are ignored for brevity
- * emit(data)
- * delay(30_000)
- * }
- * }
- * }
- *
- * // A retrying data fetcher with doubling back-off
- * val user = liveData {
- * var backOffTime = 1_000
- * var succeeded = false
- * while(!succeeded) {
- * try {
- * emit(api.fetch(id))
- * succeeded = true
- * } catch(ioError : IOException) {
- * delay(backOffTime)
- * backOffTime *= minOf(backOffTime * 2, 60_000)
- * }
- * }
- * }
- *
- * // a LiveData that tries to load the `User` from local cache first and then tries to fetch
- * // from the server and also yields the updated value
- * val user = liveData {
- * // dispatch loading first
- * emit(LOADING(id))
- * // check local storage
- * val cached = cache.loadUser(id)
- * if (cached != null) {
- * emit(cached)
- * }
- * if (cached == null || cached.isStale()) {
- * val fresh = api.fetch(id) // errors are ignored for brevity
- * cache.save(fresh)
- * emit(fresh)
- * }
- * }
- *
- * // a LiveData that immediately receives a LiveData<User> from the database and yields it as a
- * // source but also tries to back-fill the database from the server
- * val user = liveData {
- * val fromDb: LiveData<User> = roomDatabase.loadUser(id)
- * emitSource(fromDb)
- * val updated = api.fetch(id) // errors are ignored for brevity
- * // Since we are using Room here, updating the database will update the `fromDb` LiveData
- * // that was obtained above. See Room's documentation for more details.
- * // https://developer.android.com/training/data-storage/room/accessing-data#query-observable
- * roomDatabase.insert(updated)
- * }
- * ```
- *
- * * @param context The CoroutineContext to run the given block in. Defaults to
- * [EmptyCoroutineContext] combined with [Dispatchers.Main].
- * @param timeout The timeout duration before cancelling the block if there are no active observers
- * ([LiveData.hasActiveObservers].
- * @param block The block to run when the [LiveData] has active observers.
- */
-@RequiresApi(Build.VERSION_CODES.O)
-@UseExperimental(ExperimentalTypeInference::class)
-fun <T> liveData(
- context: CoroutineContext = EmptyCoroutineContext,
- timeout: Duration,
- @BuilderInference block: suspend LiveDataScope<T>.() -> Unit
-): LiveData<T> = CoroutineLiveData(context, timeout.toMillis(), block)
diff --git a/lifecycle/lifecycle-livedata-ktx/src/main/java/androidx/lifecycle/FlowLiveData.kt b/lifecycle/lifecycle-livedata-ktx/src/main/java/androidx/lifecycle/FlowLiveData.kt
index 3b2c2f25..f3d3207 100644
--- a/lifecycle/lifecycle-livedata-ktx/src/main/java/androidx/lifecycle/FlowLiveData.kt
+++ b/lifecycle/lifecycle-livedata-ktx/src/main/java/androidx/lifecycle/FlowLiveData.kt
@@ -14,8 +14,12 @@
* limitations under the License.
*/
+@file:JvmName("FlowLiveDataConversions")
+
package androidx.lifecycle
+import android.os.Build
+import androidx.annotation.RequiresApi
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
@@ -25,6 +29,7 @@
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.collect
import kotlinx.coroutines.flow.flow
+import java.time.Duration
import kotlin.coroutines.CoroutineContext
import kotlin.coroutines.EmptyCoroutineContext
@@ -56,6 +61,7 @@
* @param timeoutInMs The timeout in ms before cancelling the block if there are no active observers
* ([LiveData.hasActiveObservers]. Defaults to [DEFAULT_TIMEOUT].
*/
+@JvmOverloads
fun <T> Flow<T>.asLiveData(
context: CoroutineContext = EmptyCoroutineContext,
timeoutInMs: Long = DEFAULT_TIMEOUT
@@ -93,4 +99,38 @@
removeObserver(observer)
}
}
-}
\ No newline at end of file
+}
+
+/**
+ * Creates a LiveData that has values collected from the origin [Flow].
+ *
+ * The upstream flow collection starts when the returned [LiveData] becomes active
+ * ([LiveData.onActive]).
+ * If the [LiveData] becomes inactive ([LiveData.onInactive]) while the flow has not completed,
+ * the flow collection will be cancelled after [timeout] unless the [LiveData]
+ * becomes active again before that timeout (to gracefully handle cases like Activity rotation).
+ *
+ * After a cancellation, if the [LiveData] becomes active again, the upstream flow collection will
+ * be re-executed.
+ *
+ * If the upstream flow completes successfully *or* is cancelled due to reasons other than
+ * [LiveData] becoming inactive, it *will not* be re-collected even after [LiveData] goes through
+ * active inactive cycle.
+ *
+ * If flow completes with an exception, then exception will be delivered to the
+ * [CoroutineExceptionHandler][kotlinx.coroutines.CoroutineExceptionHandler] of provided [context].
+ * By default [EmptyCoroutineContext] is used to so an exception will be delivered to main's
+ * thread [UncaughtExceptionHandler][Thread.UncaughtExceptionHandler]. If your flow upstream is
+ * expected to throw, you can use [catch operator][kotlinx.coroutines.flow.catch] on upstream flow
+ * to emit a helpful error object.
+ *
+ * @param context The CoroutineContext to collect the upstream flow in. Defaults to
+ * [EmptyCoroutineContext] combined with [Dispatchers.Main]
+ * @param timeout The timeout in ms before cancelling the block if there are no active observers
+ * ([LiveData.hasActiveObservers]. Defaults to [DEFAULT_TIMEOUT].
+ */
+@RequiresApi(Build.VERSION_CODES.O)
+fun <T> Flow<T>.asLiveData(
+ context: CoroutineContext = EmptyCoroutineContext,
+ timeout: Duration
+): LiveData<T> = asLiveData(context, timeout.toMillis())
\ No newline at end of file
diff --git a/lifecycle/lifecycle-livedata-ktx/src/main/java/androidx/lifecycle/FlowLiveDataApi26.kt b/lifecycle/lifecycle-livedata-ktx/src/main/java/androidx/lifecycle/FlowLiveDataApi26.kt
deleted file mode 100644
index ac2643d..0000000
--- a/lifecycle/lifecycle-livedata-ktx/src/main/java/androidx/lifecycle/FlowLiveDataApi26.kt
+++ /dev/null
@@ -1,59 +0,0 @@
-/*
- * 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.lifecycle
-
-import android.os.Build
-import androidx.annotation.RequiresApi
-import kotlinx.coroutines.Dispatchers
-import kotlinx.coroutines.flow.Flow
-import java.time.Duration
-import kotlin.coroutines.CoroutineContext
-import kotlin.coroutines.EmptyCoroutineContext
-
-/**
- * Creates a LiveData that has values collected from the origin [Flow].
- *
- * The upstream flow collection starts when the returned [LiveData] becomes active
- * ([LiveData.onActive]).
- * If the [LiveData] becomes inactive ([LiveData.onInactive]) while the flow has not completed,
- * the flow collection will be cancelled after [timeout] unless the [LiveData]
- * becomes active again before that timeout (to gracefully handle cases like Activity rotation).
- *
- * After a cancellation, if the [LiveData] becomes active again, the upstream flow collection will
- * be re-executed.
- *
- * If the upstream flow completes successfully *or* is cancelled due to reasons other than
- * [LiveData] becoming inactive, it *will not* be re-collected even after [LiveData] goes through
- * active inactive cycle.
- *
- * If flow completes with an exception, then exception will be delivered to the
- * [CoroutineExceptionHandler][kotlinx.coroutines.CoroutineExceptionHandler] of provided [context].
- * By default [EmptyCoroutineContext] is used to so an exception will be delivered to main's
- * thread [UncaughtExceptionHandler][Thread.UncaughtExceptionHandler]. If your flow upstream is
- * expected to throw, you can use [catch operator][kotlinx.coroutines.flow.catch] on upstream flow
- * to emit a helpful error object.
- *
- * @param context The CoroutineContext to collect the upstream flow in. Defaults to
- * [EmptyCoroutineContext] combined with [Dispatchers.Main]
- * @param timeout The timeout in ms before cancelling the block if there are no active observers
- * ([LiveData.hasActiveObservers]. Defaults to [DEFAULT_TIMEOUT].
- */
-@RequiresApi(Build.VERSION_CODES.O)
-fun <T> Flow<T>.asLiveData(
- context: CoroutineContext = EmptyCoroutineContext,
- timeout: Duration
-): LiveData<T> = asLiveData(context, timeout.toMillis())
\ No newline at end of file
diff --git a/lifecycle/lifecycle-livedata-ktx/src/test/java/androidx/lifecycle/LiveDataFlowJavaTest.java b/lifecycle/lifecycle-livedata-ktx/src/test/java/androidx/lifecycle/LiveDataFlowJavaTest.java
new file mode 100644
index 0000000..c44f948
--- /dev/null
+++ b/lifecycle/lifecycle-livedata-ktx/src/test/java/androidx/lifecycle/LiveDataFlowJavaTest.java
@@ -0,0 +1,38 @@
+/*
+ * 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.lifecycle;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+import kotlinx.coroutines.flow.Flow;
+
+@RunWith(JUnit4.class)
+public class LiveDataFlowJavaTest {
+
+ /**
+ * A purpose of this function only to show case java interop.
+ * Real tests are in {@link FlowAsLiveDataTest} and {@link LiveDataAsFlowTest}
+ */
+ @Test
+ public void noOp() {
+ LiveData<String> liveData = new MutableLiveData<>("no-op");
+ Flow<String> flow = FlowLiveDataConversions.asFlow(liveData);
+ FlowLiveDataConversions.asLiveData(flow);
+ }
+}
diff --git a/media/build.gradle b/media/build.gradle
index 913a501..facba79 100644
--- a/media/build.gradle
+++ b/media/build.gradle
@@ -9,7 +9,7 @@
}
dependencies {
- api("androidx.core:core:1.1.0-rc01")
+ api("androidx.core:core:1.1.0")
implementation("androidx.collection:collection:1.0.0")
androidTestImplementation(ANDROIDX_TEST_EXT_JUNIT)
diff --git a/media2/common/src/main/java/androidx/media2/common/SessionPlayer.java b/media2/common/src/main/java/androidx/media2/common/SessionPlayer.java
index c67153a..848da58 100644
--- a/media2/common/src/main/java/androidx/media2/common/SessionPlayer.java
+++ b/media2/common/src/main/java/androidx/media2/common/SessionPlayer.java
@@ -343,6 +343,9 @@
/**
* Seeks to the specified position. Moves the playback head to the specified position.
* <p>
+ * The position is the relative position based on the {@link MediaItem#getStartPosition()}. So
+ * calling {@link #seekTo(long)} with {@code 0} means the seek to the start position.
+ * <p>
* On success, a {@link PlayerResult} should be returned with the current media item when the
* command completed. If it's called in {@link #PLAYER_STATE_IDLE}, it is ignored and
* a {@link PlayerResult} should be returned with
@@ -405,13 +408,18 @@
/**
* Gets the current playback head position.
+ * <p>
+ * The position is the relative position based on the {@link MediaItem#getStartPosition()}.
+ * So the position {@code 0} means the start position of the {@link MediaItem}.
*
* @return the current playback position in ms, or {@link #UNKNOWN_TIME} if unknown.
*/
public abstract long getCurrentPosition();
/**
- * Gets the duration of the current media item, or {@link #UNKNOWN_TIME} if unknown.
+ * Gets the duration of the current media item, or {@link #UNKNOWN_TIME} if unknown. If the
+ * current {@link MediaItem} has either start or end position, then duration would be adjusted
+ * accordingly instead of returning the whole size of the {@link MediaItem}.
*
* @return the duration in ms, or {@link #UNKNOWN_TIME}.
*/
@@ -419,6 +427,9 @@
/**
* Gets the position for how much has been buffered, or {@link #UNKNOWN_TIME} if unknown.
+ * <p>
+ * The position is the relative position based on the {@link MediaItem#getStartPosition()}.
+ * So the position {@code 0} means the start position of the {@link MediaItem}.
*
* @return the buffered position in ms, or {@link #UNKNOWN_TIME}.
*/
diff --git a/media2/integration-tests/testapp/build.gradle b/media2/integration-tests/testapp/build.gradle
index b269e6a..ffefd37 100644
--- a/media2/integration-tests/testapp/build.gradle
+++ b/media2/integration-tests/testapp/build.gradle
@@ -30,7 +30,7 @@
implementation(project(":media2:media2-player"))
implementation(project(":media2:media2-widget"))
implementation("androidx.appcompat:appcompat:1.0.2")
- implementation("androidx.core:core:1.1.0-rc01")
+ implementation("androidx.core:core:1.1.0")
}
android {
diff --git a/media2/player/src/main/java/androidx/media2/player/MediaPlayer.java b/media2/player/src/main/java/androidx/media2/player/MediaPlayer.java
index 4142751..ff506fa 100644
--- a/media2/player/src/main/java/androidx/media2/player/MediaPlayer.java
+++ b/media2/player/src/main/java/androidx/media2/player/MediaPlayer.java
@@ -94,11 +94,11 @@
* {@link #PLAYER_STATE_IDLE}.
* <p>
* Here's the table of automatic audio focus behavior with audio attributes.
- * <table>
+ * <table summary="Audio focus handling overview">
* <tr><th>Audio Attributes</th><th>Audio Focus Gain Type</th><th>Misc</th></tr>
* <tr><td>{@link AudioAttributesCompat#USAGE_VOICE_COMMUNICATION_SIGNALLING}</td>
* <td>{@link android.media.AudioManager#AUDIOFOCUS_NONE}</td>
- * <td /></tr>
+ * <td></td></tr>
* <tr><td><ul><li>{@link AudioAttributesCompat#USAGE_GAME}</li>
* <li>{@link AudioAttributesCompat#USAGE_MEDIA}</li>
* <li>{@link AudioAttributesCompat#USAGE_UNKNOWN}</li></ul></td>
@@ -108,7 +108,7 @@
* <tr><td><ul><li>{@link AudioAttributesCompat#USAGE_ALARM}</li>
* <li>{@link AudioAttributesCompat#USAGE_VOICE_COMMUNICATION}</li></ul></td>
* <td>{@link android.media.AudioManager#AUDIOFOCUS_GAIN_TRANSIENT}</td>
- * <td /></tr>
+ * <td></td></tr>
* <tr><td><ul><li>{@link AudioAttributesCompat#USAGE_ASSISTANCE_NAVIGATION_GUIDANCE}</li>
* <li>{@link AudioAttributesCompat#USAGE_ASSISTANCE_SONIFICATION}</li>
* <li>{@link AudioAttributesCompat#USAGE_NOTIFICATION}</li>
@@ -118,15 +118,15 @@
* <li>{@link AudioAttributesCompat#USAGE_NOTIFICATION_EVENT}</li>
* <li>{@link AudioAttributesCompat#USAGE_NOTIFICATION_RINGTONE}</li></ul></td>
* <td>{@link android.media.AudioManager#AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK}</td>
- * <td /></tr>
+ * <td></td></tr>
* <tr><td><ul><li>{@link AudioAttributesCompat#USAGE_ASSISTANT}</li></ul></td>
* <td>{@link android.media.AudioManager#AUDIOFOCUS_GAIN_TRANSIENT_EXCLUSIVE}</td>
- * <td /></tr>
+ * <td></td></tr>
* <tr><td>{@link AudioAttributesCompat#USAGE_ASSISTANCE_ACCESSIBILITY}</td>
* <td>{@link android.media.AudioManager#AUDIOFOCUS_GAIN_TRANSIENT} if
* {@link AudioAttributesCompat#CONTENT_TYPE_SPEECH},
* {@link android.media.AudioManager#AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK} otherwise</td>
- * <td /></tr>
+ * <td></td></tr>
* <tr><td>{@code null}</td>
* <td>No audio focus handling, and sets the player volume to {@code 0}</td>
* <td>Only valid if your media contents don't have audio</td></tr>
@@ -414,10 +414,11 @@
public @interface SeekMode {}
/**
- * The return value of {@link #getSelectedTrack} when there is no selected track for the given
- * type.
+ * The return value of {@link #getSelectedTrack(int)} when there is no selected track
+ * for the given type.
+ *
* @see #getSelectedTrack(int)
- * @deprecated {@link #getSelectedTrack} returns {@code null} instead of this value.
+ * @deprecated {@link #getSelectedTrack(int)} returns {@code null} instead of this value.
*/
@Deprecated
public static final int NO_TRACK_SELECTED = Integer.MIN_VALUE;
@@ -2153,9 +2154,9 @@
/**
* Returns the audio session ID.
*
- * @return the audio session ID. {@see #setAudioSessionId(int)}
- * Note that the audio session ID is 0 if a problem occurred when the MediaPlayer was
- * constructed or it is closed.
+ * @return the audio session ID. See {@link #setAudioSessionId(int)}. Note that the audio
+ * session ID is 0 if a problem occurred when the MediaPlayer was constructed or it is
+ * closed.
*/
public int getAudioSessionId() {
synchronized (mStateLock) {
@@ -2217,10 +2218,9 @@
* <p>By default the send level is 0, so even if an effect is attached to the player
* this method must be called for the effect to be applied.
* <p>Note that the passed level value is a raw scalar. UI controls should be scaled
- * logarithmically: the gain applied by audio framework ranges from -72dB to 0dB,
- * so an appropriate conversion from linear UI input x to level is:
- * x == 0 -> level = 0
- * 0 < x <= R -> level = 10^(72*(x-R)/20/R)
+ * logarithmically: the gain applied by audio framework ranges from -72dB to 0dB, so an
+ * appropriate conversion from linear UI input x to level is: x == 0 -> level = 0, 0 < x
+ * <= R -> level = 10^(72*(x-R)/20/R)
* <p>
* On success, a {@link SessionPlayer.PlayerResult} is returned with
* the current media item when the command completed.
diff --git a/media2/session/api/1.1.0-alpha01.ignore b/media2/session/api/1.1.0-alpha01.ignore
new file mode 100644
index 0000000..a5f5b4a
--- /dev/null
+++ b/media2/session/api/1.1.0-alpha01.ignore
@@ -0,0 +1,3 @@
+// Baseline format: 1.0
+RemovedInterface: androidx.media2.session.SessionResult:
+ Class androidx.media2.session.SessionResult no longer implements androidx.versionedparcelable.VersionedParcelable
diff --git a/media2/session/api/1.1.0-alpha01.txt b/media2/session/api/1.1.0-alpha01.txt
index d16e2fb..83a9ba0 100644
--- a/media2/session/api/1.1.0-alpha01.txt
+++ b/media2/session/api/1.1.0-alpha01.txt
@@ -353,7 +353,7 @@
method public androidx.media2.session.SessionCommandGroup.Builder removeCommand(androidx.media2.session.SessionCommand);
}
- public class SessionResult implements androidx.media2.common.BaseResult androidx.versionedparcelable.VersionedParcelable {
+ public class SessionResult extends androidx.versionedparcelable.CustomVersionedParcelable implements androidx.media2.common.BaseResult {
ctor public SessionResult(int, android.os.Bundle?);
method public long getCompletionTime();
method public android.os.Bundle? getCustomCommandResult();
diff --git a/media2/session/api/current.txt b/media2/session/api/current.txt
index d16e2fb..83a9ba0 100644
--- a/media2/session/api/current.txt
+++ b/media2/session/api/current.txt
@@ -353,7 +353,7 @@
method public androidx.media2.session.SessionCommandGroup.Builder removeCommand(androidx.media2.session.SessionCommand);
}
- public class SessionResult implements androidx.media2.common.BaseResult androidx.versionedparcelable.VersionedParcelable {
+ public class SessionResult extends androidx.versionedparcelable.CustomVersionedParcelable implements androidx.media2.common.BaseResult {
ctor public SessionResult(int, android.os.Bundle?);
method public long getCompletionTime();
method public android.os.Bundle? getCustomCommandResult();
diff --git a/media2/session/src/main/java/androidx/media2/session/ConnectionResult.java b/media2/session/src/main/java/androidx/media2/session/ConnectionResult.java
index 2982d35..e22bbe6 100644
--- a/media2/session/src/main/java/androidx/media2/session/ConnectionResult.java
+++ b/media2/session/src/main/java/androidx/media2/session/ConnectionResult.java
@@ -54,8 +54,10 @@
PendingIntent mSessionActivity;
@ParcelField(3)
int mPlayerState;
- @ParcelField(4)
+ @NonParcelField
MediaItem mCurrentMediaItem;
+ @ParcelField(4)
+ MediaItem mParcelableCurrentMediaItem;
@ParcelField(5)
long mPositionEventTimeMs;
@ParcelField(6)
@@ -235,11 +237,14 @@
@Override
public void onPreParceling(boolean isStream) {
mSessionBinder = (IBinder) mSessionStub;
+ mParcelableCurrentMediaItem = MediaUtils.upcastForPreparceling(mCurrentMediaItem);
}
@Override
public void onPostParceling() {
mSessionStub = IMediaSession.Stub.asInterface(mSessionBinder);
mSessionBinder = null;
+ mCurrentMediaItem = mParcelableCurrentMediaItem;
+ mParcelableCurrentMediaItem = null;
}
}
diff --git a/media2/session/src/main/java/androidx/media2/session/LibraryResult.java b/media2/session/src/main/java/androidx/media2/session/LibraryResult.java
index c1dd2d4..def0bc2 100644
--- a/media2/session/src/main/java/androidx/media2/session/LibraryResult.java
+++ b/media2/session/src/main/java/androidx/media2/session/LibraryResult.java
@@ -72,8 +72,10 @@
int mResultCode;
@ParcelField(2)
long mCompletionTime;
- @ParcelField(3)
+ @NonParcelField
MediaItem mItem;
+ @ParcelField(3)
+ MediaItem mParcelableItem;
@ParcelField(4)
MediaLibraryService.LibraryParams mParams;
// Mark list of media items NonParcelField to send the list through the ParcelImpListSlice.
@@ -221,6 +223,7 @@
@RestrictTo(LIBRARY)
@Override
public void onPreParceling(boolean isStream) {
+ mParcelableItem = MediaUtils.upcastForPreparceling(mItem);
mItemListSlice = MediaUtils.convertMediaItemListToParcelImplListSlice(mItemList);
}
@@ -230,6 +233,8 @@
@RestrictTo(LIBRARY)
@Override
public void onPostParceling() {
+ mItem = mParcelableItem;
+ mParcelableItem = null;
mItemList = MediaUtils.convertParcelImplListSliceToMediaItemList(mItemListSlice);
mItemListSlice = null;
}
diff --git a/media2/session/src/main/java/androidx/media2/session/MediaUtils.java b/media2/session/src/main/java/androidx/media2/session/MediaUtils.java
index 1b2b775..f9148f3 100644
--- a/media2/session/src/main/java/androidx/media2/session/MediaUtils.java
+++ b/media2/session/src/main/java/androidx/media2/session/MediaUtils.java
@@ -130,6 +130,24 @@
}
/**
+ * Upcasts a {@link MediaItem} to the {@link MediaItem} type for pre-parceling. Note that
+ * {@link MediaItem}'s subclass object cannot be parceled due to the security issue.
+ *
+ * @param item an item
+ * @return
+ */
+ // TODO(b/139255697): Provide the functionality in the media2-common.
+ public static MediaItem upcastForPreparceling(MediaItem item) {
+ if (item == null || item.getClass() == MediaItem.class) {
+ return item;
+ }
+ return new MediaItem.Builder()
+ .setStartPosition(item.getStartPosition())
+ .setEndPosition(item.getEndPosition())
+ .setMetadata(item.getMetadata()).build();
+ }
+
+ /**
* Creates a {@link MediaBrowserCompat.MediaItem} from the {@link MediaItem}.
*
* @param item2 an item.
@@ -805,7 +823,7 @@
*/
@NonNull
public static SessionCommandGroup convertToSessionCommandGroup(long sessionFlags,
- PlaybackStateCompat state) {
+ @Nullable PlaybackStateCompat state) {
SessionCommandGroup.Builder commandsBuilder = new SessionCommandGroup.Builder();
commandsBuilder.addAllPlayerBasicCommands(COMMAND_VERSION_CURRENT);
boolean includePlaylistCommands = (sessionFlags & FLAG_HANDLES_QUEUE_COMMANDS) != 0;
@@ -836,7 +854,7 @@
* @return custom layout. Always non-null.
*/
@NonNull
- public static List<CommandButton> convertToCustomLayout(PlaybackStateCompat state) {
+ public static List<CommandButton> convertToCustomLayout(@Nullable PlaybackStateCompat state) {
List<CommandButton> layout = new ArrayList<>();
if (state == null) {
return layout;
diff --git a/media2/session/src/main/java/androidx/media2/session/SessionResult.java b/media2/session/src/main/java/androidx/media2/session/SessionResult.java
index e9a8301..998b4c0 100644
--- a/media2/session/src/main/java/androidx/media2/session/SessionResult.java
+++ b/media2/session/src/main/java/androidx/media2/session/SessionResult.java
@@ -16,6 +16,7 @@
package androidx.media2.session;
+import static androidx.annotation.RestrictTo.Scope.LIBRARY;
import static androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX;
import android.os.Bundle;
@@ -29,8 +30,9 @@
import androidx.concurrent.futures.ResolvableFuture;
import androidx.media2.common.MediaItem;
import androidx.media2.common.SessionPlayer;
+import androidx.versionedparcelable.CustomVersionedParcelable;
+import androidx.versionedparcelable.NonParcelField;
import androidx.versionedparcelable.ParcelField;
-import androidx.versionedparcelable.VersionedParcelable;
import androidx.versionedparcelable.VersionedParcelize;
import com.google.common.util.concurrent.ListenableFuture;
@@ -43,7 +45,7 @@
* {@link MediaSession} and {@link MediaController}.
*/
@VersionedParcelize
-public class SessionResult implements RemoteResult, VersionedParcelable {
+public class SessionResult extends CustomVersionedParcelable implements RemoteResult {
/**
* Result code representing that the command is successfully completed.
* <p>
@@ -84,8 +86,10 @@
long mCompletionTime;
@ParcelField(3)
Bundle mCustomCommandResult;
- @ParcelField(4)
+ @NonParcelField
MediaItem mItem;
+ @ParcelField(4)
+ MediaItem mParcelableItem;
/**
* Constructor to be used by {@link MediaSession.SessionCallback#onCustomCommand(
@@ -208,4 +212,23 @@
public MediaItem getMediaItem() {
return mItem;
}
+
+ /**
+ * @hide
+ */
+ @RestrictTo(LIBRARY)
+ @Override
+ public void onPreParceling(boolean isStream) {
+ mParcelableItem = MediaUtils.upcastForPreparceling(mItem);
+ }
+
+ /**
+ * @hide
+ */
+ @RestrictTo(LIBRARY)
+ @Override
+ public void onPostParceling() {
+ mItem = mParcelableItem;
+ mParcelableItem = null;
+ }
}
diff --git a/media2/session/version-compat-tests/current/service/src/androidTest/java/androidx/media2/test/service/MediaSessionProviderService.java b/media2/session/version-compat-tests/current/service/src/androidTest/java/androidx/media2/test/service/MediaSessionProviderService.java
index accb8aa..69e1734 100644
--- a/media2/session/version-compat-tests/current/service/src/androidTest/java/androidx/media2/test/service/MediaSessionProviderService.java
+++ b/media2/session/version-compat-tests/current/service/src/androidTest/java/androidx/media2/test/service/MediaSessionProviderService.java
@@ -226,12 +226,10 @@
ParcelImplListSlice listSlice = config.getParcelable(KEY_PLAYLIST);
if (listSlice != null) {
- localPlayer.mPlaylist = MediaTestUtils.convertToMediaItems(listSlice.getList(),
- false /* createItem */);
+ localPlayer.mPlaylist = MediaTestUtils.convertToMediaItems(listSlice.getList());
}
- ParcelImpl currentItem = config.getParcelable(KEY_MEDIA_ITEM);
- localPlayer.mCurrentMediaItem = (currentItem == null)
- ? null : (MediaItem) MediaParcelUtils.fromParcelable(currentItem);
+ localPlayer.mCurrentMediaItem =
+ MediaTestUtils.convertToMediaItem(config.getParcelable(KEY_MEDIA_ITEM));
localPlayer.mMetadata = ParcelUtils.getVersionedParcelable(config, KEY_METADATA);
ParcelImpl videoSize = config.getParcelable(KEY_VIDEO_SIZE);
if (videoSize != null) {
@@ -439,7 +437,7 @@
throws RemoteException {
MediaSession session = mSessionMap.get(sessionId);
MockPlayer player = (MockPlayer) session.getPlayer();
- player.mPlaylist = MediaTestUtils.convertToMediaItems(playlist, false /* createItem */);
+ player.mPlaylist = MediaTestUtils.convertToMediaItems(playlist);
}
@Override
diff --git a/media2/session/version-compat-tests/current/service/src/androidTest/java/androidx/media2/test/service/MediaTestUtils.java b/media2/session/version-compat-tests/current/service/src/androidTest/java/androidx/media2/test/service/MediaTestUtils.java
index e1a375d..7e3fa85 100644
--- a/media2/session/version-compat-tests/current/service/src/androidTest/java/androidx/media2/test/service/MediaTestUtils.java
+++ b/media2/session/version-compat-tests/current/service/src/androidTest/java/androidx/media2/test/service/MediaTestUtils.java
@@ -21,6 +21,7 @@
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
+import android.net.Uri;
import android.os.Bundle;
import android.os.ParcelFileDescriptor;
import android.util.Log;
@@ -30,6 +31,7 @@
import androidx.media2.common.MediaItem;
import androidx.media2.common.MediaMetadata;
import androidx.media2.common.MediaParcelUtils;
+import androidx.media2.common.UriMediaItem;
import androidx.media2.session.MediaLibraryService.LibraryParams;
import androidx.media2.session.MediaSession;
import androidx.media2.session.MediaSession.ControllerInfo;
@@ -133,28 +135,54 @@
.build();
}
- public static List<MediaItem> convertToMediaItems(List<ParcelImpl> list,
- boolean createItem) {
+ /**
+ * Converts the List of {@link ParcelImpl} to the list of {@link MediaItem} with the
+ * {@link #convertToMediaItem(ParcelImpl)}.
+ * <p>
+ * Use this API in the {@link MediaSessionProviderService} to handle incoming initialization
+ * requests from the RemoteMediaSession. It would help to test {@link MediaItem}'s subclass
+ * instance across the process.
+ *
+ * @param list
+ * @return
+ */
+ public static List<MediaItem> convertToMediaItems(List<ParcelImpl> list) {
if (list == null) {
return null;
}
List<MediaItem> result = new ArrayList<>();
- if (createItem) {
- for (ParcelImpl parcel : list) {
- MediaItem item = MediaParcelUtils.fromParcelable(parcel);
- result.add(new FileMediaItem.Builder(ParcelFileDescriptor.adoptFd(-1))
- .setMetadata(item.getMetadata())
- .build());
- }
- } else {
- for (ParcelImpl parcel : list) {
- result.add((MediaItem) MediaParcelUtils.fromParcelable(parcel));
- }
+ for (ParcelImpl parcel : list) {
+ result.add(convertToMediaItem(parcel));
}
return result;
}
+ /**
+ * Converts the {@link ParcelImpl} to {@link MediaItem}, by creating {@link UriMediaItem}.
+ * <p>
+ * Use this API in the {@link MediaSessionProviderService} to handle incoming initialization
+ * requests from the RemoteMediaSession. It would help to test {@link MediaItem}'s subclass
+ * instance across the process.
+ *
+ * @param item
+ * @return
+ */
+ public static MediaItem convertToMediaItem(ParcelImpl item) {
+ if (item == null) {
+ return null;
+ }
+ MediaItem mediaItem = MediaParcelUtils.fromParcelable(item);
+ if (mediaItem == null) {
+ return null;
+ }
+ String mediaId = mediaItem.getMediaId();
+ return new UriMediaItem.Builder(Uri.parse("android://" + mediaId))
+ .setStartPosition(mediaItem.getStartPosition())
+ .setEndPosition(mediaItem.getEndPosition())
+ .setMetadata(mediaItem.getMetadata()).build();
+ }
+
public static ControllerInfo getTestControllerInfo(MediaSession session) {
if (session == null) {
return null;
diff --git a/media2/session/version-compat-tests/current/service/src/androidTest/java/androidx/media2/test/service/tests/MediaSession_KeyEventTest.java b/media2/session/version-compat-tests/current/service/src/androidTest/java/androidx/media2/test/service/tests/MediaSession_KeyEventTest.java
index 9976d50..8228a4a 100644
--- a/media2/session/version-compat-tests/current/service/src/androidTest/java/androidx/media2/test/service/tests/MediaSession_KeyEventTest.java
+++ b/media2/session/version-compat-tests/current/service/src/androidTest/java/androidx/media2/test/service/tests/MediaSession_KeyEventTest.java
@@ -60,6 +60,10 @@
public class MediaSession_KeyEventTest extends MediaSessionTestBase {
private static String sExpectedControllerPackageName;
+ // Intentionally member variable to prevent GC while playback is running.
+ // Should be only used on the sHandler.
+ private MediaPlayer mMediaPlayer;
+
private AudioManager mAudioManager;
private MediaSession mSession;
private MockPlayer mPlayer;
@@ -90,35 +94,50 @@
.setSessionCallback(sHandlerExecutor, mSessionCallback)
.build();
- // Make this test to get priority for handling media key event
- // SDK < 26: Playback state should become *playing*
- mPlayer.notifyPlayerStateChanged(SessionPlayer.PLAYER_STATE_PLAYING);
-
- // SDK >= 26: Play a media item in the same process of the session.
- // Target raw resource should be short enough to finish within the time limit of @SmallTest.
- final CountDownLatch latch = new CountDownLatch(1);
- sHandler.postAndSync(new Runnable() {
- @Override
- public void run() {
- // Pick the shortest media.
- final MediaPlayer player = MediaPlayer.create(mContext, R.raw.camera_click);
- player.setOnCompletionListener(new OnCompletionListener() {
- @Override
- public void onCompletion(MediaPlayer mp) {
- latch.countDown();
- player.release();
- }
- });
- player.start();
- }
- });
- assertTrue(latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
+ // Make this test to get priority for handling media key event.
+ // Here's the requirement for an app to receive media key events via MediaSession.
+ // SDK < 26: Playback state should become *playing* for receiving key events.
+ // SDK >= 26: Play a media item in the same process of the session for receiving key
+ // events.
+ if (Build.VERSION.SDK_INT < 26) {
+ mPlayer.notifyPlayerStateChanged(SessionPlayer.PLAYER_STATE_PLAYING);
+ } else {
+ final CountDownLatch latch = new CountDownLatch(1);
+ sHandler.postAndSync(new Runnable() {
+ @Override
+ public void run() {
+ // Pick the shortest media to finish within the TIMEOUT_MS.
+ mMediaPlayer = MediaPlayer.create(mContext, R.raw.camera_click);
+ mMediaPlayer.setOnCompletionListener(new OnCompletionListener() {
+ @Override
+ public void onCompletion(MediaPlayer mp) {
+ if (mMediaPlayer != null) {
+ mMediaPlayer.release();
+ mMediaPlayer = null;
+ latch.countDown();
+ }
+ }
+ });
+ mMediaPlayer.start();
+ }
+ });
+ assertTrue(latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
+ }
}
@After
@Override
public void cleanUp() throws Exception {
super.cleanUp();
+ sHandler.postAndSync(new Runnable() {
+ @Override
+ public void run() {
+ if (mMediaPlayer != null) {
+ mMediaPlayer.release();
+ mMediaPlayer = null;
+ }
+ }
+ });
mSession.close();
}
diff --git a/media2/session/version-compat-tests/previous/service/src/androidTest/java/androidx/media2/test/service/tests/MediaSession_KeyEventTest.java b/media2/session/version-compat-tests/previous/service/src/androidTest/java/androidx/media2/test/service/tests/MediaSession_KeyEventTest.java
index b605f9b..bef96da 100644
--- a/media2/session/version-compat-tests/previous/service/src/androidTest/java/androidx/media2/test/service/tests/MediaSession_KeyEventTest.java
+++ b/media2/session/version-compat-tests/previous/service/src/androidTest/java/androidx/media2/test/service/tests/MediaSession_KeyEventTest.java
@@ -59,6 +59,10 @@
public class MediaSession_KeyEventTest extends MediaSessionTestBase {
private static String sExpectedControllerPackageName;
+ // Intentionally member variable to prevent GC while playback is running.
+ // Should be only used on the sHandler.
+ private MediaPlayer mMediaPlayer;
+
private AudioManager mAudioManager;
private MediaSession mSession;
private MockPlayer mPlayer;
@@ -93,31 +97,50 @@
// SDK < 26: Playback state should become *playing*
mPlayer.notifyPlayerStateChanged(SessionPlayer.PLAYER_STATE_PLAYING);
- // SDK >= 26: Play a media item in the same process of the session.
- // Target raw resource should be short enough to finish within the time limit of @SmallTest.
- final CountDownLatch latch = new CountDownLatch(1);
- sHandler.postAndSync(new Runnable() {
- @Override
- public void run() {
- // Pick the shortest media.
- final MediaPlayer player = MediaPlayer.create(mContext, R.raw.camera_click);
- player.setOnCompletionListener(new OnCompletionListener() {
- @Override
- public void onCompletion(MediaPlayer mp) {
- latch.countDown();
- player.release();
- }
- });
- player.start();
- }
- });
- assertTrue(latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
+ // Make this test to get priority for handling media key event.
+ // Here's the requirement for an app to receive media key events via MediaSession.
+ // SDK < 26: Playback state should become *playing* for receiving key events.
+ // SDK >= 26: Play a media item in the same process of the session for receiving key
+ // events.
+ if (Build.VERSION.SDK_INT < 26) {
+ mPlayer.notifyPlayerStateChanged(SessionPlayer.PLAYER_STATE_PLAYING);
+ } else {
+ final CountDownLatch latch = new CountDownLatch(1);
+ sHandler.postAndSync(new Runnable() {
+ @Override
+ public void run() {
+ // Pick the shortest media to finish within the TIMEOUT_MS.
+ mMediaPlayer = MediaPlayer.create(mContext, R.raw.camera_click);
+ mMediaPlayer.setOnCompletionListener(new OnCompletionListener() {
+ @Override
+ public void onCompletion(MediaPlayer mp) {
+ if (mMediaPlayer != null) {
+ mMediaPlayer.release();
+ mMediaPlayer = null;
+ latch.countDown();
+ }
+ }
+ });
+ mMediaPlayer.start();
+ }
+ });
+ assertTrue(latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
+ }
}
@After
@Override
public void cleanUp() throws Exception {
super.cleanUp();
+ sHandler.postAndSync(new Runnable() {
+ @Override
+ public void run() {
+ if (mMediaPlayer != null) {
+ mMediaPlayer.release();
+ mMediaPlayer = null;
+ }
+ }
+ });
mSession.close();
}
diff --git a/media2/widget/api/1.0.0-rc02.txt b/media2/widget/api/1.0.0-rc02.txt
new file mode 100644
index 0000000..b614f1a
--- /dev/null
+++ b/media2/widget/api/1.0.0-rc02.txt
@@ -0,0 +1,38 @@
+// Signature format: 3.0
+package androidx.media2.widget {
+
+ public class MediaControlView extends android.view.ViewGroup {
+ ctor public MediaControlView(android.content.Context);
+ ctor public MediaControlView(android.content.Context, android.util.AttributeSet?);
+ ctor public MediaControlView(android.content.Context, android.util.AttributeSet?, int);
+ method public void requestPlayButtonFocus();
+ method public void setMediaController(androidx.media2.session.MediaController);
+ method public void setOnFullScreenListener(androidx.media2.widget.MediaControlView.OnFullScreenListener?);
+ method public void setPlayer(androidx.media2.common.SessionPlayer);
+ }
+
+ public static interface MediaControlView.OnFullScreenListener {
+ method public void onFullScreen(android.view.View, boolean);
+ }
+
+ public class VideoView extends android.view.ViewGroup {
+ ctor public VideoView(android.content.Context);
+ ctor public VideoView(android.content.Context, android.util.AttributeSet?);
+ ctor public VideoView(android.content.Context, android.util.AttributeSet?, int);
+ method public androidx.media2.widget.MediaControlView? getMediaControlView();
+ method public int getViewType();
+ method public void setMediaControlView(androidx.media2.widget.MediaControlView, long);
+ method public void setMediaController(androidx.media2.session.MediaController);
+ method public void setOnViewTypeChangedListener(androidx.media2.widget.VideoView.OnViewTypeChangedListener?);
+ method public void setPlayer(androidx.media2.common.SessionPlayer);
+ method public void setViewType(int);
+ field public static final int VIEW_TYPE_SURFACEVIEW = 0; // 0x0
+ field public static final int VIEW_TYPE_TEXTUREVIEW = 1; // 0x1
+ }
+
+ public static interface VideoView.OnViewTypeChangedListener {
+ method public void onViewTypeChanged(android.view.View, int);
+ }
+
+}
+
diff --git a/media2/widget/api/res-1.0.0-rc02.txt b/media2/widget/api/res-1.0.0-rc02.txt
new file mode 100644
index 0000000..9015818
--- /dev/null
+++ b/media2/widget/api/res-1.0.0-rc02.txt
@@ -0,0 +1,2 @@
+attr enableControlView
+attr viewType
diff --git a/media2/widget/api/restricted_1.0.0-rc02.txt b/media2/widget/api/restricted_1.0.0-rc02.txt
new file mode 100644
index 0000000..b614f1a
--- /dev/null
+++ b/media2/widget/api/restricted_1.0.0-rc02.txt
@@ -0,0 +1,38 @@
+// Signature format: 3.0
+package androidx.media2.widget {
+
+ public class MediaControlView extends android.view.ViewGroup {
+ ctor public MediaControlView(android.content.Context);
+ ctor public MediaControlView(android.content.Context, android.util.AttributeSet?);
+ ctor public MediaControlView(android.content.Context, android.util.AttributeSet?, int);
+ method public void requestPlayButtonFocus();
+ method public void setMediaController(androidx.media2.session.MediaController);
+ method public void setOnFullScreenListener(androidx.media2.widget.MediaControlView.OnFullScreenListener?);
+ method public void setPlayer(androidx.media2.common.SessionPlayer);
+ }
+
+ public static interface MediaControlView.OnFullScreenListener {
+ method public void onFullScreen(android.view.View, boolean);
+ }
+
+ public class VideoView extends android.view.ViewGroup {
+ ctor public VideoView(android.content.Context);
+ ctor public VideoView(android.content.Context, android.util.AttributeSet?);
+ ctor public VideoView(android.content.Context, android.util.AttributeSet?, int);
+ method public androidx.media2.widget.MediaControlView? getMediaControlView();
+ method public int getViewType();
+ method public void setMediaControlView(androidx.media2.widget.MediaControlView, long);
+ method public void setMediaController(androidx.media2.session.MediaController);
+ method public void setOnViewTypeChangedListener(androidx.media2.widget.VideoView.OnViewTypeChangedListener?);
+ method public void setPlayer(androidx.media2.common.SessionPlayer);
+ method public void setViewType(int);
+ field public static final int VIEW_TYPE_SURFACEVIEW = 0; // 0x0
+ field public static final int VIEW_TYPE_TEXTUREVIEW = 1; // 0x1
+ }
+
+ public static interface VideoView.OnViewTypeChangedListener {
+ method public void onViewTypeChanged(android.view.View, int);
+ }
+
+}
+
diff --git a/media2/widget/build.gradle b/media2/widget/build.gradle
index 19cc8f8..45725fc 100644
--- a/media2/widget/build.gradle
+++ b/media2/widget/build.gradle
@@ -29,7 +29,7 @@
api("androidx.media2:media2-session:1.0.0-rc01")
implementation("androidx.appcompat:appcompat:1.0.2")
implementation("androidx.palette:palette:1.0.0")
- implementation("androidx.concurrent:concurrent-futures:1.0.0-beta01")
+ implementation("androidx.concurrent:concurrent-futures:1.0.0-rc01")
androidTestImplementation(ANDROIDX_TEST_EXT_JUNIT)
androidTestImplementation(ANDROIDX_TEST_CORE)
diff --git a/media2/widget/src/androidTest/java/androidx/media2/widget/MediaControlView_WithPlayerTest.java b/media2/widget/src/androidTest/java/androidx/media2/widget/MediaControlView_WithPlayerTest.java
index e0ddf03..3df4b6d 100644
--- a/media2/widget/src/androidTest/java/androidx/media2/widget/MediaControlView_WithPlayerTest.java
+++ b/media2/widget/src/androidTest/java/androidx/media2/widget/MediaControlView_WithPlayerTest.java
@@ -23,6 +23,7 @@
import static androidx.test.espresso.matcher.ViewMatchers.isClickable;
import static androidx.test.espresso.matcher.ViewMatchers.isCompletelyDisplayed;
import static androidx.test.espresso.matcher.ViewMatchers.isDisplayed;
+import static androidx.test.espresso.matcher.ViewMatchers.isEnabled;
import static androidx.test.espresso.matcher.ViewMatchers.withContentDescription;
import static androidx.test.espresso.matcher.ViewMatchers.withId;
import static androidx.test.espresso.matcher.ViewMatchers.withText;
@@ -43,7 +44,6 @@
import androidx.media2.common.SessionPlayer;
import androidx.media2.common.SessionPlayer.TrackInfo;
import androidx.media2.session.MediaController;
-import androidx.media2.widget.test.R;
import androidx.test.filters.LargeTest;
import androidx.test.rule.ActivityTestRule;
@@ -89,10 +89,11 @@
@Before
public void setup() throws Throwable {
mActivity = mActivityRule.getActivity();
- mMediaControlView = mActivity.findViewById(R.id.mediacontrolview);
+ mMediaControlView = mActivity.findViewById(
+ androidx.media2.widget.test.R.id.mediacontrolview);
Uri fileSchemeUri = Uri.parse("android.resource://" + mContext.getPackageName() + "/"
- + R.raw.test_file_scheme_video);
+ + androidx.media2.widget.test.R.raw.test_file_scheme_video);
mFileSchemeMediaItem = createTestMediaItem(fileSchemeUri);
setKeepScreenOn(mActivityRule);
@@ -301,29 +302,31 @@
assertTrue(callback.mItemLatch.await(WAIT_TIME_MS, TimeUnit.MILLISECONDS));
assertEquals(0, playerWrapper.getCurrentMediaItemIndex());
- onView(allOf(withId(R.id.prev), not(isClickable())));
- onView(allOf(withId(R.id.next), isClickable()));
+ onView(allOf(withId(R.id.prev), isCompletelyDisplayed()))
+ .check(matches(not(isEnabled())));
+ onView(allOf(withId(R.id.next), isCompletelyDisplayed())).check(matches(isEnabled()));
callback.mItemLatch = new CountDownLatch(1);
onView(allOf(withId(R.id.next), isCompletelyDisplayed())).perform(click());
assertTrue(callback.mItemLatch.await(WAIT_TIME_MS, TimeUnit.MILLISECONDS));
assertEquals(1, playerWrapper.getCurrentMediaItemIndex());
- onView(allOf(withId(R.id.prev), isClickable()));
- onView(allOf(withId(R.id.next), isClickable()));
+ onView(allOf(withId(R.id.prev), isCompletelyDisplayed())).check(matches(isEnabled()));
+ onView(allOf(withId(R.id.next), isCompletelyDisplayed())).check(matches(isEnabled()));
callback.mItemLatch = new CountDownLatch(1);
onView(allOf(withId(R.id.next), isCompletelyDisplayed())).perform(click());
assertTrue(callback.mItemLatch.await(WAIT_TIME_MS, TimeUnit.MILLISECONDS));
assertEquals(2, playerWrapper.getCurrentMediaItemIndex());
- onView(allOf(withId(R.id.prev), isClickable()));
- onView(allOf(withId(R.id.next), not(isClickable())));
+ onView(allOf(withId(R.id.prev), isCompletelyDisplayed())).check(matches(isEnabled()));
+ onView(allOf(withId(R.id.next), isCompletelyDisplayed()))
+ .check(matches(not(isEnabled())));
callback.mItemLatch = new CountDownLatch(1);
onView(allOf(withId(R.id.prev), isCompletelyDisplayed())).perform(click());
assertTrue(callback.mItemLatch.await(WAIT_TIME_MS, TimeUnit.MILLISECONDS));
assertEquals(1, playerWrapper.getCurrentMediaItemIndex());
- onView(allOf(withId(R.id.prev), isClickable()));
- onView(allOf(withId(R.id.next), isClickable()));
+ onView(allOf(withId(R.id.prev), isCompletelyDisplayed())).check(matches(isEnabled()));
+ onView(allOf(withId(R.id.next), isCompletelyDisplayed())).check(matches(isEnabled()));
}
@Test
@@ -352,7 +355,7 @@
@Test
public void testButtonVisibilityForMusicFile() throws Throwable {
Uri uri = Uri.parse("android.resource://" + mContext.getPackageName() + "/"
- + R.raw.test_music);
+ + androidx.media2.widget.test.R.raw.test_music);
final MediaItem uriMediaItem = createTestMediaItem(uri);
final CountDownLatch latch = new CountDownLatch(1);
@@ -376,7 +379,7 @@
@Test
public void testUpdateAndSelectSubtitleTrack() throws Throwable {
Uri uri = Uri.parse("android.resource://" + mContext.getPackageName() + "/"
- + R.raw.testvideo_with_2_subtitle_tracks);
+ + androidx.media2.widget.test.R.raw.testvideo_with_2_subtitle_tracks);
final String subtitleTrackOffText = mContext.getResources().getString(
R.string.MediaControlView_subtitle_off_text);
@@ -468,7 +471,8 @@
mActivityRule.runOnUiThread(new Runnable() {
@Override
public void run() {
- ViewGroup layout = mActivity.findViewById(R.id.framelayout);
+ ViewGroup layout = mActivity.findViewById(
+ androidx.media2.widget.test.R.id.framelayout);
layout.removeView(mMediaControlView);
mMediaControlView = new MediaControlView(mActivity);
if (playerWrapper.mPlayer != null) {
diff --git a/media2/widget/src/androidTest/java/androidx/media2/widget/MediaControlView_WithoutPlayerTest.java b/media2/widget/src/androidTest/java/androidx/media2/widget/MediaControlView_WithoutPlayerTest.java
index fed585d..9f2642a 100644
--- a/media2/widget/src/androidTest/java/androidx/media2/widget/MediaControlView_WithoutPlayerTest.java
+++ b/media2/widget/src/androidTest/java/androidx/media2/widget/MediaControlView_WithoutPlayerTest.java
@@ -29,7 +29,6 @@
import android.view.View;
import androidx.annotation.NonNull;
-import androidx.media2.widget.test.R;
import androidx.test.annotation.UiThreadTest;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.LargeTest;
@@ -60,7 +59,8 @@
@Before
public void setup() throws Throwable {
mActivity = mActivityRule.getActivity();
- mMediaControlView = mActivity.findViewById(R.id.mediacontrolview);
+ mMediaControlView = mActivity.findViewById(
+ androidx.media2.widget.test.R.id.mediacontrolview);
setKeepScreenOn(mActivityRule);
checkAttachedToWindow(mMediaControlView);
diff --git a/media2/widget/src/androidTest/java/androidx/media2/widget/MediaWidgetTestBase.java b/media2/widget/src/androidTest/java/androidx/media2/widget/MediaWidgetTestBase.java
index e005a01..19b36f11 100644
--- a/media2/widget/src/androidTest/java/androidx/media2/widget/MediaWidgetTestBase.java
+++ b/media2/widget/src/androidTest/java/androidx/media2/widget/MediaWidgetTestBase.java
@@ -141,6 +141,8 @@
return list;
}
+ // TODO(b/138091975) Do not ignore returned Futures if feasible.
+ @SuppressWarnings("FutureReturnValueIgnored")
PlayerWrapper createPlayerWrapperOfController(@NonNull PlayerWrapper.PlayerCallback callback,
@Nullable MediaItem item, @Nullable List<MediaItem> playlist) {
prepareLooper();
@@ -169,6 +171,8 @@
return wrapper;
}
+ // TODO(b/138091975) Do not ignore returned Futures if feasible.
+ @SuppressWarnings("FutureReturnValueIgnored")
PlayerWrapper createPlayerWrapperOfPlayer(@NonNull PlayerWrapper.PlayerCallback callback,
@Nullable MediaItem item, @Nullable List<MediaItem> playlist) {
SessionPlayer player = new MediaPlayer(mContext);
diff --git a/media2/widget/src/androidTest/res/values/themes.xml b/media2/widget/src/androidTest/res/values/themes.xml
index 2843f5e..ff1c3ab 100644
--- a/media2/widget/src/androidTest/res/values/themes.xml
+++ b/media2/widget/src/androidTest/res/values/themes.xml
@@ -18,7 +18,7 @@
<resources>
<style name="HasWindowTitle">
- <item name="windowNoTitle">false</item>
+ <item name="android:windowNoTitle">false</item>
</style>
</resources>
\ No newline at end of file
diff --git a/navigation/dynamic-feature-navigator/build.gradle b/navigation/dynamic-feature-navigator/build.gradle
new file mode 100644
index 0000000..2e7d439
--- /dev/null
+++ b/navigation/dynamic-feature-navigator/build.gradle
@@ -0,0 +1,78 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import static androidx.build.dependencies.DependenciesKt.*
+import androidx.build.LibraryGroups
+import androidx.build.LibraryVersions
+import androidx.build.AndroidXExtension
+import androidx.build.Publish
+
+plugins {
+ id("AndroidXPlugin")
+ id("com.android.library")
+ id("kotlin-android")
+}
+
+android {
+ lintOptions {
+ fatal("UnknownNullness")
+ }
+}
+
+dependencies {
+ api("androidx.core:core:1.0.1")
+ implementation(ANDROIDX_COLLECTION)
+ implementation(project(":navigation:navigation-runtime"))
+ implementation("com.google.android.play:core:1.6.1")
+
+ testImplementation(ANDROIDX_TEST_CORE)
+ testImplementation(ANDROIDX_TEST_EXT_JUNIT)
+ testImplementation(ANDROIDX_TEST_RUNNER)
+ testImplementation(JUNIT)
+ testImplementation(KOTLIN_STDLIB)
+ testImplementation(MOCKITO_CORE)
+ testImplementation(TRUTH)
+
+ androidTestImplementation(ANDROIDX_TEST_CORE)
+ androidTestImplementation(ANDROIDX_TEST_EXT_JUNIT)
+ androidTestImplementation(ANDROIDX_TEST_RUNNER)
+ androidTestImplementation(DEXMAKER_MOCKITO, libs.exclude_bytebuddy)
+ androidTestImplementation(ESPRESSO_CORE)
+ androidTestImplementation(KOTLIN_STDLIB)
+ androidTestImplementation(MOCKITO_CORE, libs.exclude_bytebuddy)
+ androidTestImplementation(TRUTH)
+}
+
+//used by testCompile safe-args-generator
+android.libraryVariants.all { variant ->
+ def name = variant.name
+ def suffix = name.capitalize()
+ project.tasks.create(name: "jar${suffix}", type: Jar){
+ dependsOn variant.javaCompileProvider.get()
+ from variant.javaCompileProvider.get().destinationDir
+ destinationDir new File(project.buildDir, "libJar")
+ }
+}
+
+androidx {
+ name = "Android Dynamic Feature Navigation"
+ publish = Publish.NONE
+ mavenVersion = LibraryVersions.NAVIGATION
+ mavenGroup = LibraryGroups.NAVIGATION
+ inceptionYear = "2019"
+ description = "Android Dynamic Feature Navigation"
+ url = AndroidXExtension.ARCHITECTURE_URL
+}
diff --git a/navigation/dynamic-feature-navigator/src/main/AndroidManifest.xml b/navigation/dynamic-feature-navigator/src/main/AndroidManifest.xml
new file mode 100644
index 0000000..47db05b
--- /dev/null
+++ b/navigation/dynamic-feature-navigator/src/main/AndroidManifest.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2017 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.
+ -->
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="androidx.navigation.dynamicfeature">
+
+</manifest>
diff --git a/navigation/dynamic-feature-navigator/src/main/res-public/values/public_attrs.xml b/navigation/dynamic-feature-navigator/src/main/res-public/values/public_attrs.xml
new file mode 100644
index 0000000..ad99bc5
--- /dev/null
+++ b/navigation/dynamic-feature-navigator/src/main/res-public/values/public_attrs.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright 2018 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.
+ -->
+
+<!-- Definitions of attributes to be exposed as public -->
+<resources>
+ <public type="attr" name="argType"/>
+ <public type="attr" name="destination"/>
+ <public type="attr" name="enterAnim"/>
+ <public type="attr" name="exitAnim"/>
+ <public type="attr" name="launchSingleTop"/>
+ <public type="attr" name="nullable"/>
+ <public type="attr" name="popUpTo"/>
+ <public type="attr" name="popUpToInclusive"/>
+ <public type="attr" name="popEnterAnim"/>
+ <public type="attr" name="popExitAnim"/>
+ <public type="attr" name="startDestination"/>
+ <public type="attr" name="uri"/>
+</resources>
diff --git a/navigation/dynamic-feature-navigator/src/main/res/values/attrs.xml b/navigation/dynamic-feature-navigator/src/main/res/values/attrs.xml
new file mode 100644
index 0000000..e2c6182
--- /dev/null
+++ b/navigation/dynamic-feature-navigator/src/main/res/values/attrs.xml
@@ -0,0 +1,18 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2017 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.
+ -->
+<resources>
+ <declare-styleable name="DynamicNavigator">
+ <attr name="moduleName" format="string|reference"/>
+ </declare-styleable>
+</resources>
diff --git a/navigation/dynamic-feature-navigator/src/test/java/androidx/navigation/ActionOnlyNavDirectionsTest.kt b/navigation/dynamic-feature-navigator/src/test/java/androidx/navigation/ActionOnlyNavDirectionsTest.kt
new file mode 100644
index 0000000..51a4fca
--- /dev/null
+++ b/navigation/dynamic-feature-navigator/src/test/java/androidx/navigation/ActionOnlyNavDirectionsTest.kt
@@ -0,0 +1,44 @@
+/*
+ * 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.navigation
+
+import androidx.test.filters.SmallTest
+import com.google.common.truth.Truth.assertThat
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+
+@RunWith(JUnit4::class)
+@SmallTest
+class ActionOnlyNavDirectionsTest {
+
+ @Test
+ fun testEquals() {
+ assertThat(ActionOnlyNavDirections(1))
+ .isEqualTo(ActionOnlyNavDirections(1))
+ assertThat(ActionOnlyNavDirections(1))
+ .isNotEqualTo(ActionOnlyNavDirections(2))
+ }
+
+ @Test
+ fun testHashCode() {
+ assertThat(ActionOnlyNavDirections(1).hashCode())
+ .isEqualTo(ActionOnlyNavDirections(1).hashCode())
+ assertThat(ActionOnlyNavDirections(1).hashCode())
+ .isNotEqualTo(ActionOnlyNavDirections(2).hashCode())
+ }
+}
\ No newline at end of file
diff --git a/navigation/dynamic-feature-navigator/src/test/java/androidx/navigation/NavActionTest.kt b/navigation/dynamic-feature-navigator/src/test/java/androidx/navigation/NavActionTest.kt
new file mode 100644
index 0000000..a5b393b
--- /dev/null
+++ b/navigation/dynamic-feature-navigator/src/test/java/androidx/navigation/NavActionTest.kt
@@ -0,0 +1,58 @@
+/*
+ * Copyright (C) 2017 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.navigation
+
+import androidx.annotation.IdRes
+import androidx.test.filters.SmallTest
+import com.google.common.truth.Truth.assertThat
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+
+@RunWith(JUnit4::class)
+@SmallTest
+class NavActionTest {
+
+ companion object {
+ @IdRes
+ private const val DESTINATION_ID = 1
+ }
+
+ @Test
+ fun createAction() {
+ val action = NavAction(DESTINATION_ID)
+
+ assertThat(action.destinationId).isEqualTo(DESTINATION_ID)
+ }
+
+ @Test
+ fun createActionWithNullNavOptions() {
+ val action = NavAction(DESTINATION_ID, null)
+
+ assertThat(action.destinationId).isEqualTo(DESTINATION_ID)
+ assertThat(action.navOptions).isNull()
+ }
+
+ @Test
+ fun setNavOptions() {
+ val action = NavAction(DESTINATION_ID)
+ val navOptions = NavOptions.Builder().build()
+ action.navOptions = navOptions
+
+ assertThat(action.navOptions).isEqualTo(navOptions)
+ }
+}
diff --git a/navigation/dynamic-feature-navigator/src/test/java/androidx/navigation/NavDestinationTest.kt b/navigation/dynamic-feature-navigator/src/test/java/androidx/navigation/NavDestinationTest.kt
new file mode 100644
index 0000000..848caed
--- /dev/null
+++ b/navigation/dynamic-feature-navigator/src/test/java/androidx/navigation/NavDestinationTest.kt
@@ -0,0 +1,209 @@
+/*
+ * Copyright (C) 2017 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.navigation
+
+import android.content.Context
+import androidx.annotation.IdRes
+import androidx.test.filters.SmallTest
+import com.google.common.truth.Truth.assertThat
+import org.junit.Assert.fail
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+import org.mockito.Mockito.`when`
+import org.mockito.Mockito.mock
+
+@RunWith(JUnit4::class)
+@SmallTest
+class NavDestinationTest {
+
+ companion object {
+ @IdRes
+ private const val INVALID_ACTION_ID = 0
+ @IdRes
+ private const val ACTION_ID = 1
+ @IdRes
+ private const val DESTINATION_ID = 1
+ }
+
+ @Test
+ fun parseClassFromNameAbsolute() {
+ val context = mock(Context::class.java)
+ val clazz = NavDestination.parseClassFromName(context,
+ "java.lang.String", Any::class.java)
+ assertThat(clazz).isNotNull()
+ assertThat(clazz.name).isEqualTo(String::class.java.name)
+ }
+
+ @Test
+ fun parseClassFromNameAbsoluteInvalid() {
+ val context = mock(Context::class.java)
+ try {
+ NavDestination.parseClassFromName(context,
+ "definitely.not.found", Any::class.java)
+ fail("Invalid type should cause an IllegalArgumentException")
+ } catch (e: IllegalArgumentException) {
+ // Expected
+ }
+ }
+
+ @Test
+ fun parseClassFromNameAbsoluteWithType() {
+ val context = mock(Context::class.java)
+ val clazz = NavDestination.parseClassFromName(context,
+ "java.lang.String", String::class.java)
+ assertThat(clazz).isNotNull()
+ assertThat(clazz.name).isEqualTo(String::class.java.name)
+ }
+
+ @Test
+ fun parseClassFromNameAbsoluteWithIncorrectType() {
+ val context = mock(Context::class.java)
+ try {
+ NavDestination.parseClassFromName(context,
+ "java.lang.String", List::class.java)
+ fail("Incorrect type should cause an IllegalArgumentException")
+ } catch (e: IllegalArgumentException) {
+ // Expected
+ }
+ }
+
+ @Test
+ fun parseClassFromNameRelative() {
+ val context = mock(Context::class.java)
+ `when`(context.packageName).thenReturn("java.lang")
+ val clazz = NavDestination.parseClassFromName(context,
+ ".String", Any::class.java)
+ assertThat(clazz).isNotNull()
+ assertThat(clazz.name).isEqualTo(String::class.java.name)
+ }
+
+ @Test
+ fun parseClassFromNameRelativeInvalid() {
+ val context = mock(Context::class.java)
+ `when`(context.packageName).thenReturn("java.lang")
+ try {
+ NavDestination.parseClassFromName(context,
+ ".definitely.not.found", Any::class.java)
+ fail("Invalid type should cause an IllegalArgumentException")
+ } catch (e: IllegalArgumentException) {
+ // Expected
+ }
+ }
+
+ @Test
+ fun parseClassFromNameRelativeWithType() {
+ val context = mock(Context::class.java)
+ `when`(context.packageName).thenReturn("java.lang")
+ val clazz = NavDestination.parseClassFromName(context,
+ ".String", String::class.java)
+ assertThat(clazz).isNotNull()
+ assertThat(clazz.name).isEqualTo(String::class.java.name)
+ }
+
+ @Test
+ fun parseClassFromNameRelativeWithIncorrectType() {
+ val context = mock(Context::class.java)
+ `when`(context.packageName).thenReturn("java.lang")
+ try {
+ NavDestination.parseClassFromName(context,
+ ".String", List::class.java)
+ fail("Incorrect type should cause an IllegalArgumentException")
+ } catch (e: IllegalArgumentException) {
+ // Expected
+ }
+ }
+
+ @Test
+ fun buildDeepLinkIds() {
+ val destination = NoOpNavigator().createDestination()
+ destination.id = DESTINATION_ID
+ val parentId = 2
+ val navGraphNavigator = NavGraphNavigator(mock(NavigatorProvider::class.java))
+ val parent = navGraphNavigator.createDestination().apply {
+ id = parentId
+ }
+ destination.parent = parent
+ val deepLinkIds = destination.buildDeepLinkIds()
+ assertThat(deepLinkIds.size).isEqualTo(2)
+ assertThat(deepLinkIds).asList().containsExactly(parentId, DESTINATION_ID)
+ }
+
+ @Test
+ fun putActionByDestinationId() {
+ val destination = NoOpNavigator().createDestination()
+ destination.putAction(ACTION_ID, DESTINATION_ID)
+
+ val action = destination.getAction(ACTION_ID)
+ assertThat(action).isNotNull()
+ assertThat(action?.destinationId).isEqualTo(DESTINATION_ID)
+ }
+
+ @Test(expected = IllegalArgumentException::class)
+ fun putActionWithInvalidDestinationId() {
+ val destination = NoOpNavigator().createDestination()
+ destination.putAction(INVALID_ACTION_ID, DESTINATION_ID)
+ }
+
+ @Test
+ fun putAction() {
+ val destination = NoOpNavigator().createDestination()
+ val action = NavAction(DESTINATION_ID)
+ destination.putAction(ACTION_ID, action)
+
+ assertThat(destination.getAction(ACTION_ID)).isEqualTo(action)
+ }
+
+ @Test
+ fun removeAction() {
+ val destination = NoOpNavigator().createDestination()
+ val action = NavAction(DESTINATION_ID)
+ destination.putAction(ACTION_ID, action)
+
+ assertThat(destination.getAction(ACTION_ID)).isEqualTo(action)
+
+ destination.removeAction(ACTION_ID)
+
+ assertThat(destination.getAction(ACTION_ID)).isNull()
+ }
+
+ @Test
+ fun addArgument() {
+ val destination = NoOpNavigator().createDestination()
+ val stringArgument = NavArgument.Builder()
+ .setType(NavType.StringType)
+ .build()
+ destination.addArgument("stringArg", stringArgument)
+ assertThat(destination.arguments.size).isEqualTo(1)
+ assertThat(destination.arguments.get("stringArg")).isEqualTo(stringArgument)
+ }
+
+ @Test
+ fun removeArgument() {
+ val destination = NoOpNavigator().createDestination()
+ val stringArgument = NavArgument.Builder()
+ .setType(NavType.StringType)
+ .build()
+ destination.addArgument("stringArg", stringArgument)
+ assertThat(destination.arguments.size).isEqualTo(1)
+ assertThat(destination.arguments.get("stringArg")).isEqualTo(stringArgument)
+
+ destination.removeArgument("stringArg")
+ assertThat(destination.arguments.size).isEqualTo(0)
+ assertThat(destination.arguments.get("stringArg")).isNull()
+ }
+}
diff --git a/navigation/dynamic-feature-navigator/src/test/java/androidx/navigation/NavGraphNavigatorTest.kt b/navigation/dynamic-feature-navigator/src/test/java/androidx/navigation/NavGraphNavigatorTest.kt
new file mode 100644
index 0000000..7b3269f
--- /dev/null
+++ b/navigation/dynamic-feature-navigator/src/test/java/androidx/navigation/NavGraphNavigatorTest.kt
@@ -0,0 +1,145 @@
+/*
+ * Copyright 2018 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.navigation
+
+import androidx.annotation.IdRes
+import androidx.test.filters.SmallTest
+import com.google.common.truth.Truth.assertThat
+import com.google.common.truth.Truth.assertWithMessage
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+
+@RunWith(JUnit4::class)
+@SmallTest
+class NavGraphNavigatorTest {
+
+ companion object {
+ @IdRes
+ private const val FIRST_DESTINATION_ID = 1
+ @IdRes
+ private const val SECOND_DESTINATION_ID = 2
+ }
+
+ private lateinit var provider: NavigatorProvider
+ private lateinit var noOpNavigator: NoOpNavigator
+ private lateinit var navGraphNavigator: NavGraphNavigator
+
+ @Before
+ fun setup() {
+ provider = NavigatorProvider().apply {
+ addNavigator(NoOpNavigator().also { noOpNavigator = it })
+ addNavigator(NavGraphNavigator(this).also {
+ navGraphNavigator = it
+ })
+ }
+ }
+
+ private fun createFirstDestination() = noOpNavigator.createDestination().apply {
+ id = FIRST_DESTINATION_ID
+ }
+
+ private fun createSecondDestination() = noOpNavigator.createDestination().apply {
+ id = SECOND_DESTINATION_ID
+ }
+
+ private fun createGraphWithDestination(
+ destination: NavDestination,
+ startId: Int = destination.id
+ ) = navGraphNavigator.createDestination().apply {
+ addDestination(destination)
+ startDestination = startId
+ }
+
+ @Test(expected = IllegalStateException::class)
+ fun navigateWithoutStartDestination() {
+ val destination = createFirstDestination()
+ val graph = createGraphWithDestination(destination, startId = 0)
+ navGraphNavigator.navigate(graph, null, null, null)
+ }
+
+ @Test
+ fun navigate() {
+ val destination = createFirstDestination()
+ val graph = createGraphWithDestination(destination)
+ assertThat(navGraphNavigator.navigate(graph, null, null, null))
+ .isEqualTo(destination)
+ }
+
+ @Test
+ fun navigateThenPop() {
+ val destination = createFirstDestination()
+ val graph = createGraphWithDestination(destination)
+ assertThat(navGraphNavigator.navigate(graph, null, null, null))
+ .isEqualTo(destination)
+ val success = navGraphNavigator.popBackStack()
+ assertWithMessage("popBackStack should return true")
+ .that(success)
+ .isTrue()
+ }
+
+ @Test
+ fun navigateSingleTopOnEmptyStack() {
+ val destination = createFirstDestination()
+ val graph = createGraphWithDestination(destination)
+ // singleTop should still show as added on an empty stack
+ assertThat(navGraphNavigator.navigate(graph, null,
+ NavOptions.Builder().setLaunchSingleTop(true).build(), null))
+ .isEqualTo(destination)
+ }
+
+ @Test
+ fun navigateSingleTop() {
+ val destination = createFirstDestination()
+ val graph = createGraphWithDestination(destination)
+ assertThat(navGraphNavigator.navigate(graph, null, null, null))
+ .isEqualTo(destination)
+ assertThat(navGraphNavigator.navigate(graph, null,
+ NavOptions.Builder().setLaunchSingleTop(true).build(), null))
+ .isEqualTo(destination)
+ }
+
+ @Test
+ fun navigateSingleTopNotTop() {
+ val destination = createFirstDestination()
+ val graph = createGraphWithDestination(destination)
+ val secondDestination = createSecondDestination()
+ val secondGraph = createGraphWithDestination(secondDestination).apply {
+ id = SECOND_DESTINATION_ID
+ }
+ assertThat(navGraphNavigator.navigate(graph, null, null, null))
+ .isEqualTo(destination)
+ assertThat(navGraphNavigator.navigate(secondGraph, null,
+ NavOptions.Builder().setLaunchSingleTop(true).build(), null))
+ .isEqualTo(secondDestination)
+ }
+
+ @Test
+ fun navigateSingleTopNested() {
+ val destination = createFirstDestination()
+ val nestedGraph = createGraphWithDestination(destination).apply {
+ id = FIRST_DESTINATION_ID
+ }
+ val graph = createGraphWithDestination(nestedGraph)
+ assertThat(navGraphNavigator.navigate(graph, null, null, null))
+ .isEqualTo(destination)
+ assertThat(navGraphNavigator.navigate(graph, null,
+ NavOptions.Builder().setLaunchSingleTop(true).build(), null))
+ .isEqualTo(destination)
+ }
+}
diff --git a/navigation/dynamic-feature-navigator/src/test/java/androidx/navigation/NavGraphTest.kt b/navigation/dynamic-feature-navigator/src/test/java/androidx/navigation/NavGraphTest.kt
new file mode 100644
index 0000000..a8ad344
--- /dev/null
+++ b/navigation/dynamic-feature-navigator/src/test/java/androidx/navigation/NavGraphTest.kt
@@ -0,0 +1,231 @@
+/*
+ * Copyright (C) 2017 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.navigation
+
+import androidx.annotation.IdRes
+import androidx.test.filters.SmallTest
+import com.google.common.truth.Truth.assertThat
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+import java.util.Arrays
+import java.util.NoSuchElementException
+
+@RunWith(JUnit4::class)
+@SmallTest
+class NavGraphTest {
+
+ companion object {
+ @IdRes
+ private const val FIRST_DESTINATION_ID = 1
+ @IdRes
+ private const val SECOND_DESTINATION_ID = 2
+ }
+
+ private lateinit var provider: NavigatorProvider
+ private lateinit var noOpNavigator: NoOpNavigator
+ private lateinit var navGraphNavigator: NavGraphNavigator
+
+ @Before
+ fun setup() {
+ provider = NavigatorProvider().apply {
+ addNavigator(NoOpNavigator().also { noOpNavigator = it })
+ addNavigator(NavGraphNavigator(this).also {
+ navGraphNavigator = it
+ })
+ }
+ }
+
+ private fun createFirstDestination() = noOpNavigator.createDestination().apply {
+ id = FIRST_DESTINATION_ID
+ }
+
+ private fun createSecondDestination() = noOpNavigator.createDestination().apply {
+ id = SECOND_DESTINATION_ID
+ }
+
+ private fun createGraphWithDestination(destination: NavDestination) =
+ navGraphNavigator.createDestination().apply {
+ addDestination(destination)
+ }
+
+ private fun createGraphWithDestinations(vararg destinations: NavDestination) =
+ navGraphNavigator.createDestination().apply {
+ addDestinations(*destinations)
+ }
+
+ @Test(expected = IllegalArgumentException::class)
+ fun addDestinationWithoutId() {
+ val graph = navGraphNavigator.createDestination()
+ val destination = noOpNavigator.createDestination()
+ graph.addDestination(destination)
+ }
+
+ @Test
+ fun addDestination() {
+ val destination = createFirstDestination()
+ val graph = createGraphWithDestination(destination)
+
+ assertThat(destination.parent).isEqualTo(graph)
+ assertThat(graph.findNode(FIRST_DESTINATION_ID)).isEqualTo(destination)
+ }
+
+ @Test
+ fun addDestinationsAsCollection() {
+ val graph = navGraphNavigator.createDestination()
+ val destination = createFirstDestination()
+ val secondDestination = createSecondDestination()
+ graph.addDestinations(Arrays.asList(destination, secondDestination))
+
+ assertThat(destination.parent).isEqualTo(graph)
+ assertThat(graph.findNode(FIRST_DESTINATION_ID)).isEqualTo(destination)
+ assertThat(secondDestination.parent).isEqualTo(graph)
+ assertThat(graph.findNode(SECOND_DESTINATION_ID)).isEqualTo(secondDestination)
+ }
+
+ @Test
+ fun addDestinationsAsVarArgs() {
+ val destination = createFirstDestination()
+ val secondDestination = createSecondDestination()
+ val graph = createGraphWithDestinations(destination, secondDestination)
+
+ assertThat(destination.parent).isEqualTo(graph)
+ assertThat(graph.findNode(FIRST_DESTINATION_ID)).isEqualTo(destination)
+ assertThat(secondDestination.parent).isEqualTo(graph)
+ assertThat(graph.findNode(SECOND_DESTINATION_ID)).isEqualTo(secondDestination)
+ }
+
+ @Test
+ fun addReplacementDestination() {
+ val destination = createFirstDestination()
+ val graph = createGraphWithDestination(destination)
+
+ val replacementDestination = noOpNavigator.createDestination()
+ replacementDestination.id = FIRST_DESTINATION_ID
+ graph.addDestination(replacementDestination)
+
+ assertThat(destination.parent).isNull()
+ assertThat(replacementDestination.parent).isEqualTo(graph)
+ assertThat(graph.findNode(FIRST_DESTINATION_ID)).isEqualTo(replacementDestination)
+ }
+
+ @Test(expected = IllegalStateException::class)
+ fun addDestinationWithExistingParent() {
+ val destination = createFirstDestination()
+ createGraphWithDestination(destination)
+
+ val other = navGraphNavigator.createDestination()
+ other.addDestination(destination)
+ }
+
+ @Test
+ fun addAll() {
+ val destination = createFirstDestination()
+ val other = createGraphWithDestination(destination)
+
+ val graph = navGraphNavigator.createDestination()
+ graph.addAll(other)
+
+ assertThat(destination.parent).isEqualTo(graph)
+ assertThat(graph.findNode(FIRST_DESTINATION_ID)).isEqualTo(destination)
+ assertThat(other.findNode(FIRST_DESTINATION_ID)).isNull()
+ }
+
+ @Test
+ fun removeDestination() {
+ val destination = createFirstDestination()
+ val graph = createGraphWithDestination(destination)
+
+ graph.remove(destination)
+
+ assertThat(destination.parent).isNull()
+ assertThat(graph.findNode(FIRST_DESTINATION_ID)).isNull()
+ }
+
+ @Test
+ operator fun iterator() {
+ val destination = createFirstDestination()
+ val secondDestination = createSecondDestination()
+ val graph = createGraphWithDestinations(destination, secondDestination)
+
+ assertThat(graph.iterator().asSequence().asIterable())
+ .containsExactly(destination, secondDestination)
+ }
+
+ @Test(expected = NoSuchElementException::class)
+ fun iteratorNoSuchElement() {
+ val destination = createFirstDestination()
+ val graph = createGraphWithDestination(destination)
+
+ val iterator = graph.iterator()
+ iterator.next()
+ iterator.next()
+ }
+
+ @Test
+ fun iteratorRemove() {
+ val destination = createFirstDestination()
+ val graph = createGraphWithDestination(destination)
+
+ val iterator = graph.iterator()
+ val value = iterator.next()
+ iterator.remove()
+ assertThat(value.parent).isNull()
+ assertThat(graph.findNode(value.id)).isNull()
+ }
+
+ @Test
+ fun iteratorDoubleRemove() {
+ val destination = createFirstDestination()
+ val secondDestination = createSecondDestination()
+ val graph = createGraphWithDestinations(destination, secondDestination)
+
+ val iterator = graph.iterator()
+ iterator.next()
+ iterator.remove()
+ val value = iterator.next()
+ iterator.remove()
+ assertThat(value.parent).isNull()
+ assertThat(graph.findNode(value.id)).isNull()
+ }
+
+ @Test(expected = IllegalStateException::class)
+ fun iteratorDoubleRemoveWithoutNext() {
+ val destination = createFirstDestination()
+ val secondDestination = createSecondDestination()
+ val graph = createGraphWithDestinations(destination, secondDestination)
+
+ val iterator = graph.iterator()
+ iterator.next()
+ iterator.remove()
+ iterator.remove()
+ }
+
+ @Test
+ fun clear() {
+ val destination = createFirstDestination()
+ val secondDestination = createSecondDestination()
+ val graph = createGraphWithDestinations(destination, secondDestination)
+
+ graph.clear()
+ assertThat(destination.parent).isNull()
+ assertThat(graph.findNode(FIRST_DESTINATION_ID)).isNull()
+ assertThat(secondDestination.parent).isNull()
+ assertThat(graph.findNode(SECOND_DESTINATION_ID)).isNull()
+ }
+}
diff --git a/navigation/dynamic-feature-navigator/src/test/java/androidx/navigation/NavigatorProviderTest.kt b/navigation/dynamic-feature-navigator/src/test/java/androidx/navigation/NavigatorProviderTest.kt
new file mode 100644
index 0000000..7f766e5
--- /dev/null
+++ b/navigation/dynamic-feature-navigator/src/test/java/androidx/navigation/NavigatorProviderTest.kt
@@ -0,0 +1,146 @@
+/*
+ * Copyright (C) 2017 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.navigation
+
+import android.os.Bundle
+import androidx.test.filters.SmallTest
+import com.google.common.truth.Truth.assertThat
+import org.junit.Assert.fail
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+
+@RunWith(JUnit4::class)
+@SmallTest
+class NavigatorProviderTest {
+ @Test
+ fun addWithMissingAnnotationName() {
+ val provider = NavigatorProvider()
+ val navigator = NoNameNavigator()
+ try {
+ provider.addNavigator(navigator)
+ fail("Adding a provider with no @Navigator.Name should cause an " +
+ "IllegalArgumentException")
+ } catch (e: IllegalArgumentException) {
+ // Expected
+ }
+ }
+
+ @Test
+ fun addWithMissingAnnotationNameGetWithExplicitName() {
+ val provider = NavigatorProvider()
+ val navigator = NoNameNavigator()
+ provider.addNavigator("name", navigator)
+ assertThat(provider.getNavigator<NoNameNavigator>("name"))
+ .isEqualTo(navigator)
+ }
+
+ @Test
+ fun addWithExplicitNameGetWithExplicitName() {
+ val provider = NavigatorProvider()
+ val navigator = EmptyNavigator()
+ provider.addNavigator("name", navigator)
+
+ assertThat(provider.getNavigator<EmptyNavigator>("name"))
+ .isEqualTo(navigator)
+ try {
+ provider.getNavigator(EmptyNavigator::class.java)
+ fail("getNavigator(Class) with an invalid name should cause an IllegalStateException")
+ } catch (e: IllegalStateException) {
+ // Expected
+ }
+ }
+
+ @Test
+ fun addWithExplicitNameGetWithMissingAnnotationName() {
+ val provider = NavigatorProvider()
+ val navigator = NoNameNavigator()
+ provider.addNavigator("name", navigator)
+ try {
+ provider.getNavigator(NoNameNavigator::class.java)
+ fail("getNavigator(Class) with no @Navigator.Name should cause an " +
+ "IllegalArgumentException")
+ } catch (e: IllegalArgumentException) {
+ // Expected
+ }
+ }
+
+ @Test
+ fun addWithAnnotationNameGetWithAnnotationName() {
+ val provider = NavigatorProvider()
+ val navigator = EmptyNavigator()
+ provider.addNavigator(navigator)
+ assertThat(provider.getNavigator(EmptyNavigator::class.java))
+ .isEqualTo(navigator)
+ }
+
+ @Test
+ fun addWithAnnotationNameGetWithExplicitName() {
+ val provider = NavigatorProvider()
+ val navigator = EmptyNavigator()
+ provider.addNavigator(navigator)
+ assertThat(provider.getNavigator<EmptyNavigator>(EmptyNavigator.NAME))
+ .isEqualTo(navigator)
+ }
+}
+
+class NoNameNavigator : Navigator<NavDestination>() {
+ override fun createDestination(): NavDestination {
+ throw IllegalStateException("createDestination is not supported")
+ }
+
+ override fun navigate(
+ destination: NavDestination,
+ args: Bundle?,
+ navOptions: NavOptions?,
+ navigatorExtras: Navigator.Extras?
+ ): NavDestination? {
+ throw IllegalStateException("navigate is not supported")
+ }
+
+ override fun popBackStack(): Boolean {
+ throw IllegalStateException("popBackStack is not supported")
+ }
+}
+
+/**
+ * An empty [Navigator] used to test [NavigatorProvider].
+ */
+@Navigator.Name(EmptyNavigator.NAME)
+internal open class EmptyNavigator : Navigator<NavDestination>() {
+
+ companion object {
+ const val NAME = "empty"
+ }
+
+ override fun createDestination(): NavDestination {
+ throw IllegalStateException("createDestination is not supported")
+ }
+
+ override fun navigate(
+ destination: NavDestination,
+ args: Bundle?,
+ navOptions: NavOptions?,
+ navigatorExtras: Navigator.Extras?
+ ): NavDestination? {
+ throw IllegalStateException("navigate is not supported")
+ }
+
+ override fun popBackStack(): Boolean {
+ throw IllegalStateException("popBackStack is not supported")
+ }
+}
diff --git a/navigation/navigation-common/src/androidTest/java/androidx/navigation/AddInDefaultArgsTest.kt b/navigation/navigation-common/src/androidTest/java/androidx/navigation/AddInDefaultArgsTest.kt
index fff7f49..a084bc3 100644
--- a/navigation/navigation-common/src/androidTest/java/androidx/navigation/AddInDefaultArgsTest.kt
+++ b/navigation/navigation-common/src/androidTest/java/androidx/navigation/AddInDefaultArgsTest.kt
@@ -17,6 +17,8 @@
package androidx.navigation
import android.os.Bundle
+import androidx.navigation.test.intArgument
+import androidx.navigation.test.stringArgument
import androidx.test.filters.SmallTest
import com.google.common.truth.Truth.assertThat
import com.google.common.truth.Truth.assertWithMessage
@@ -24,18 +26,9 @@
import org.junit.runner.RunWith
import org.junit.runners.Parameterized
-private val stringArgument = "stringArg" to NavArgument.Builder()
- .setType(NavType.StringType)
- .setIsNullable(true)
- .build()
-private val stringArgumentWithDefault = "stringArg" to NavArgument.Builder()
- .setType(NavType.StringType)
- .setDefaultValue("aaa")
- .build()
-private val intArgument = "intArg" to NavArgument.Builder()
- .setType(NavType.IntType)
- .setDefaultValue(123)
- .build()
+private val stringArgumentWithoutDefault = "stringArg" to stringArgument(true)
+private val stringArgumentWithDefault = "stringArg" to stringArgument("aaa")
+private val intArgumentWithDefault = "intArg" to intArgument(123)
@SmallTest
@RunWith(Parameterized::class)
@@ -51,11 +44,11 @@
// Test with an empty set of arguments
mapOf(),
// Test with an argument with no default value
- mapOf(stringArgument),
+ mapOf(stringArgumentWithoutDefault),
// Test with arguments where only some have default values
- mapOf(stringArgument, intArgument),
+ mapOf(stringArgumentWithoutDefault, intArgumentWithDefault),
// Test with arguments that have default values
- mapOf(stringArgumentWithDefault, intArgument)
+ mapOf(stringArgumentWithDefault, intArgumentWithDefault)
).forEach { arguments: Map<String, NavArgument> ->
// Run with a null Bundle
add(arrayOf(arguments, Bundle.EMPTY))
diff --git a/navigation/navigation-common/src/androidTest/java/androidx/navigation/NavDeepLinkTest.kt b/navigation/navigation-common/src/androidTest/java/androidx/navigation/NavDeepLinkTest.kt
index 1c3302e..b26ce1c 100644
--- a/navigation/navigation-common/src/androidTest/java/androidx/navigation/NavDeepLinkTest.kt
+++ b/navigation/navigation-common/src/androidTest/java/androidx/navigation/NavDeepLinkTest.kt
@@ -17,6 +17,9 @@
package androidx.navigation
import android.net.Uri
+import androidx.navigation.test.intArgument
+import androidx.navigation.test.nullableStringArgument
+import androidx.navigation.test.stringArgument
import androidx.test.filters.SmallTest
import com.google.common.truth.Truth.assertThat
import com.google.common.truth.Truth.assertWithMessage
@@ -110,7 +113,7 @@
val id = 2
val matchArgs = deepLink.getMatchingArguments(
Uri.parse(deepLinkArgument.replace("{id}", id.toString())),
- mapOf("id" to NavArgument.Builder().setType(NavType.IntType).build())
+ mapOf("id" to intArgument())
)
assertWithMessage("Args should not be null")
.that(matchArgs)
@@ -128,7 +131,7 @@
val id = "invalid"
val matchArgs = deepLink.getMatchingArguments(
Uri.parse(deepLinkArgument.replace("{id}", id)),
- mapOf("id" to NavArgument.Builder().setType(NavType.IntType).build())
+ mapOf("id" to intArgument())
)
assertWithMessage("Args should be null")
.that(matchArgs)
@@ -143,7 +146,7 @@
val id = 2
val matchArgs = deepLink.getMatchingArguments(
Uri.parse(deepLinkArgument.replace("{id}", id.toString())),
- mapOf("id" to NavArgument.Builder().setType(NavType.IntType).build())
+ mapOf("id" to intArgument())
)
assertWithMessage("Args should not be null")
.that(matchArgs)
@@ -161,7 +164,7 @@
val id = "invalid"
val matchArgs = deepLink.getMatchingArguments(
Uri.parse(deepLinkArgument.replace("{id}", id)),
- mapOf("id" to NavArgument.Builder().setType(NavType.IntType).build())
+ mapOf("id" to intArgument())
)
assertWithMessage("Args should be null")
.that(matchArgs)
@@ -178,12 +181,8 @@
val matchArgs = deepLink.getMatchingArguments(
Uri.parse(deepLinkArgument
.replace("{id}", id.toString()).replace("{myarg}", myarg)),
- mapOf("id" to NavArgument.Builder()
- .setType(NavType.IntType)
- .build(),
- "myarg" to NavArgument.Builder()
- .setType(NavType.StringType)
- .build())
+ mapOf("id" to intArgument(),
+ "myarg" to stringArgument())
)
assertWithMessage("Args should not be null")
.that(matchArgs)
@@ -204,10 +203,7 @@
val id = 2
val matchArgs = deepLink.getMatchingArguments(
Uri.parse("$DEEP_LINK_EXACT_HTTPS/users"),
- mapOf("id" to NavArgument.Builder()
- .setType(NavType.IntType)
- .setDefaultValue(id)
- .build())
+ mapOf("id" to intArgument(id))
)
assertWithMessage("Args should not be null")
.that(matchArgs)
@@ -224,10 +220,7 @@
val matchArgs = deepLink.getMatchingArguments(
Uri.parse("$DEEP_LINK_EXACT_HTTPS/users"),
- mapOf("myarg" to NavArgument.Builder()
- .setType(NavType.StringType)
- .setIsNullable(true)
- .build())
+ mapOf("myarg" to nullableStringArgument())
)
assertWithMessage("Args should not be null")
.that(matchArgs)
@@ -246,10 +239,7 @@
val id = 2
val matchArgs = deepLink.getMatchingArguments(
Uri.parse(deepLinkArgument),
- mapOf("id" to NavArgument.Builder()
- .setType(NavType.IntType)
- .setDefaultValue(id)
- .build())
+ mapOf("id" to intArgument(id))
)
assertWithMessage("Args should not be null")
.that(matchArgs)
@@ -267,10 +257,7 @@
val matchArgs = deepLink.getMatchingArguments(
Uri.parse(deepLinkArgument),
- mapOf("myarg" to NavArgument.Builder()
- .setType(NavType.StringType)
- .setIsNullable(true)
- .build())
+ mapOf("myarg" to nullableStringArgument())
)
assertWithMessage("Args should not be null")
.that(matchArgs)
@@ -289,13 +276,8 @@
val optional = "test"
val matchArgs = deepLink.getMatchingArguments(
Uri.parse("$DEEP_LINK_EXACT_HTTPS/users?id={id}".replace("{id}", id.toString())),
- mapOf("id" to NavArgument.Builder()
- .setType(NavType.IntType)
- .build(),
- "optional" to NavArgument.Builder()
- .setType(NavType.StringType)
- .setDefaultValue(optional)
- .build())
+ mapOf("id" to intArgument(),
+ "optional" to stringArgument(optional))
)
assertWithMessage("Args should not be null")
.that(matchArgs)
@@ -318,13 +300,8 @@
val matchArgs = deepLink.getMatchingArguments(
Uri.parse("$DEEP_LINK_EXACT_HTTPS/users?optional={optional}&id={id}"
.replace("{id}", id.toString())),
- mapOf("id" to NavArgument.Builder()
- .setType(NavType.IntType)
- .build(),
- "optional" to NavArgument.Builder()
- .setType(NavType.StringType)
- .setDefaultValue(optional)
- .build())
+ mapOf("id" to intArgument(),
+ "optional" to stringArgument(optional))
)
assertWithMessage("Args should not be null")
.that(matchArgs)
@@ -345,13 +322,8 @@
val id = 2
val matchArgs = deepLink.getMatchingArguments(
Uri.parse("$DEEP_LINK_EXACT_HTTPS/users?id={id}".replace("{id}", id.toString())),
- mapOf("id" to NavArgument.Builder()
- .setType(NavType.IntType)
- .build(),
- "optional" to NavArgument.Builder()
- .setType(NavType.StringType)
- .setIsNullable(true)
- .build())
+ mapOf("id" to intArgument(),
+ "optional" to nullableStringArgument())
)
assertWithMessage("Args should not be null")
.that(matchArgs)
@@ -374,13 +346,8 @@
deepLink.getMatchingArguments(
Uri.parse("$DEEP_LINK_EXACT_HTTPS/users?id={id}&invalid={invalid}"
.replace("{id}", id.toString())),
- mapOf("id" to NavArgument.Builder()
- .setType(NavType.IntType)
- .build(),
- "invalid" to NavArgument.Builder()
- .setType(NavType.StringType)
- .setIsNullable(true)
- .build()))
+ mapOf("id" to intArgument(),
+ "invalid" to nullableStringArgument()))
fail(
"Adding parameter that does not exists in the NavDeepLink should throw " +
"IllegalArgumentException"
@@ -403,9 +370,7 @@
val matchArgs = deepLink.getMatchingArguments(
Uri.parse("$DEEP_LINK_EXACT_HTTPS/users?string={id}"
.replace("{id}", id.toString())),
- mapOf("id" to NavArgument.Builder()
- .setType(NavType.IntType)
- .build())
+ mapOf("id" to intArgument())
)
assertWithMessage("Args should not be null")
.that(matchArgs)
@@ -422,10 +387,7 @@
val matchArgs = deepLink.getMatchingArguments(
Uri.parse("$DEEP_LINK_EXACT_HTTPS/users"),
- mapOf("myarg" to NavArgument.Builder()
- .setType(NavType.StringType)
- .setIsNullable(true)
- .build())
+ mapOf("myarg" to nullableStringArgument())
)
assertWithMessage("Args should not be null")
.that(matchArgs)
@@ -443,10 +405,7 @@
val id = 2
val matchArgs = deepLink.getMatchingArguments(
Uri.parse("$DEEP_LINK_EXACT_HTTPS/users"),
- mapOf("id" to NavArgument.Builder()
- .setType(NavType.IntType)
- .setDefaultValue(id)
- .build())
+ mapOf("id" to intArgument(id))
)
assertWithMessage("Args should not be null")
.that(matchArgs)
@@ -464,7 +423,7 @@
val id = 2
val matchArgs = deepLink.getMatchingArguments(
Uri.parse(deepLinkArgument.replace("{id}", id.toString())),
- mapOf("id" to NavArgument.Builder().setType(NavType.IntType).build())
+ mapOf("id" to intArgument())
)
assertWithMessage("Args should not be null")
.that(matchArgs)
@@ -481,10 +440,7 @@
val matchArgs = deepLink.getMatchingArguments(
Uri.parse("$DEEP_LINK_EXACT_HTTPS/users"),
- mapOf("myarg" to NavArgument.Builder()
- .setType(NavType.StringType)
- .setIsNullable(true)
- .build())
+ mapOf("myarg" to nullableStringArgument())
)
assertWithMessage("Args should not be null")
.that(matchArgs)
@@ -502,10 +458,7 @@
val id = 2
val matchArgs = deepLink.getMatchingArguments(
Uri.parse("$DEEP_LINK_EXACT_HTTPS/users"),
- mapOf("id" to NavArgument.Builder()
- .setType(NavType.IntType)
- .setDefaultValue(id)
- .build())
+ mapOf("id" to intArgument(id))
)
assertWithMessage("Args should not be null")
.that(matchArgs)
@@ -524,8 +477,8 @@
val last = "Doe"
val matchArgs = deepLink.getMatchingArguments(
Uri.parse(deepLinkArgument.replace("{first}", first).replace("{last}", last)),
- mapOf("first" to NavArgument.Builder().setType(NavType.StringType).build(),
- "last" to NavArgument.Builder().setType(NavType.StringType).build())
+ mapOf("first" to stringArgument(),
+ "last" to stringArgument())
)
assertWithMessage("Args should not be null")
.that(matchArgs)
@@ -547,14 +500,8 @@
val last = "Doe"
val matchArgs = deepLink.getMatchingArguments(
Uri.parse("$DEEP_LINK_EXACT_HTTPS/users"),
- mapOf("first" to NavArgument.Builder()
- .setType(NavType.StringType)
- .setDefaultValue(first)
- .build(),
- "last" to NavArgument.Builder()
- .setType(NavType.StringType)
- .setDefaultValue(last)
- .build())
+ mapOf("first" to stringArgument(first),
+ "last" to stringArgument(last))
)
assertWithMessage("Args should not be null")
.that(matchArgs)
@@ -576,13 +523,8 @@
val last = "Doe"
val matchArgs = deepLink.getMatchingArguments(
Uri.parse("$DEEP_LINK_EXACT_HTTPS/users?name=Jane_"),
- mapOf("first" to NavArgument.Builder()
- .setType(NavType.StringType)
- .build(),
- "last" to NavArgument.Builder()
- .setType(NavType.StringType)
- .setDefaultValue(last)
- .build())
+ mapOf("first" to stringArgument(),
+ "last" to stringArgument(last))
)
assertWithMessage("Args should not be null")
.that(matchArgs)
@@ -602,14 +544,8 @@
val matchArgs = deepLink.getMatchingArguments(
Uri.parse("$DEEP_LINK_EXACT_HTTPS/users"),
- mapOf("first" to NavArgument.Builder()
- .setType(NavType.StringType)
- .setIsNullable(true)
- .build(),
- "last" to NavArgument.Builder()
- .setType(NavType.StringType)
- .setIsNullable(true)
- .build())
+ mapOf("first" to nullableStringArgument(),
+ "last" to nullableStringArgument())
)
assertWithMessage("Args should not be null")
.that(matchArgs)
@@ -631,9 +567,7 @@
val matchArgs = deepLink.getMatchingArguments(
Uri.parse("$DEEP_LINK_EXACT_HTTPS/users?productId=wildCardMatch-{id}"
.replace("{id}", id.toString())),
- mapOf("id" to NavArgument.Builder()
- .setType(NavType.IntType)
- .build())
+ mapOf("id" to intArgument())
)
assertWithMessage("Args should not be null")
.that(matchArgs)
@@ -651,10 +585,7 @@
val id = 2
val matchArgs = deepLink.getMatchingArguments(
Uri.parse("$DEEP_LINK_EXACT_HTTPS/users"),
- mapOf("id" to NavArgument.Builder()
- .setType(NavType.IntType)
- .setDefaultValue(id)
- .build())
+ mapOf("id" to intArgument(id))
)
assertWithMessage("Args should not be null")
.that(matchArgs)
@@ -671,10 +602,7 @@
val matchArgs = deepLink.getMatchingArguments(
Uri.parse("$DEEP_LINK_EXACT_HTTPS/users?productId=wildCardMatch-{myarg}"),
- mapOf("myarg" to NavArgument.Builder()
- .setType(NavType.StringType)
- .setIsNullable(true)
- .build())
+ mapOf("myarg" to nullableStringArgument())
)
assertWithMessage("Args should not be null")
.that(matchArgs)
@@ -693,10 +621,7 @@
val id = 2
val matchArgs = deepLink.getMatchingArguments(
Uri.parse("$DEEP_LINK_EXACT_HTTPS/users?productId=.*-"),
- mapOf("id" to NavArgument.Builder()
- .setType(NavType.IntType)
- .setDefaultValue(id)
- .build())
+ mapOf("id" to intArgument(id))
)
assertWithMessage("Args should not be null")
.that(matchArgs)
@@ -715,9 +640,7 @@
val matchArgs = deepLink.getMatchingArguments(
Uri.parse("$DEEP_LINK_EXACT_HTTPS/users?productId=A*B{id}"
.replace("{id}", id.toString())),
- mapOf("id" to NavArgument.Builder()
- .setType(NavType.IntType)
- .build())
+ mapOf("id" to intArgument())
)
assertWithMessage("Args should not be null")
.that(matchArgs)
@@ -736,9 +659,7 @@
val matchArgs = deepLink.getMatchingArguments(
Uri.parse("$DEEP_LINK_EXACT_HTTPS/users?productId={id}A*B"
.replace("{id}", id.toString())),
- mapOf("id" to NavArgument.Builder()
- .setType(NavType.IntType)
- .build())
+ mapOf("id" to intArgument())
)
assertWithMessage("Args should not be null")
.that(matchArgs)
@@ -756,9 +677,7 @@
val path = "directions"
val matchArgs = deepLink.getMatchingArguments(
Uri.parse("$DEEP_LINK_EXACT_HTTPS/users?path=go/to/{path}".replace("{path}", path)),
- mapOf("path" to NavArgument.Builder()
- .setType(NavType.StringType)
- .build())
+ mapOf("path" to stringArgument())
)
assertWithMessage("Args should not be null")
.that(matchArgs)
@@ -776,10 +695,7 @@
val path = "directions"
val matchArgs = deepLink.getMatchingArguments(
Uri.parse("$DEEP_LINK_EXACT_HTTPS/users"),
- mapOf("path" to NavArgument.Builder()
- .setType(NavType.StringType)
- .setDefaultValue(path)
- .build())
+ mapOf("path" to stringArgument(path))
)
assertWithMessage("Args should not be null")
.that(matchArgs)
@@ -796,10 +712,7 @@
val matchArgs = deepLink.getMatchingArguments(
Uri.parse("$DEEP_LINK_EXACT_HTTPS/users"),
- mapOf("path" to NavArgument.Builder()
- .setType(NavType.StringType)
- .setIsNullable(true)
- .build())
+ mapOf("path" to nullableStringArgument())
)
assertWithMessage("Args should not be null")
.that(matchArgs)
@@ -818,10 +731,7 @@
val path = "directions"
val matchArgs = deepLink.getMatchingArguments(
Uri.parse("$DEEP_LINK_EXACT_HTTPS/users?path=go/to/"),
- mapOf("path" to NavArgument.Builder()
- .setType(NavType.StringType)
- .setDefaultValue(path)
- .build())
+ mapOf("path" to stringArgument(path))
)
assertWithMessage("Args should not be null")
.that(matchArgs)
@@ -840,7 +750,7 @@
val name = "John Doe"
val matchArgs = deepLink.getMatchingArguments(
Uri.parse(deepLinkArgument.replace("{name}", Uri.encode(name))),
- mapOf("name" to NavArgument.Builder().setType(NavType.StringType).build())
+ mapOf("name" to stringArgument())
)
assertWithMessage("Args should not be null")
@@ -862,8 +772,8 @@
Uri.parse(deepLinkArgument
.replace("{id}", id.toString())
.replace("{postId}", postId.toString())),
- mapOf("id" to NavArgument.Builder().setType(NavType.IntType).build(),
- "postId" to NavArgument.Builder().setType(NavType.IntType).build())
+ mapOf("id" to intArgument(),
+ "postId" to intArgument())
)
assertWithMessage("Args should not be null")
.that(matchArgs)
@@ -913,7 +823,7 @@
Uri.parse(deepLinkMultiple
.replace(".*", "test")
.replace("{postId}", postId.toString())),
- mapOf("postId" to NavArgument.Builder().setType(NavType.IntType).build())
+ mapOf("postId" to intArgument())
)
assertWithMessage("Args should not be null")
.that(matchArgs)
@@ -933,7 +843,7 @@
Uri.parse(deepLinkMultiple
.replace("{id}", id.toString())
.replace(".*", "test")),
- mapOf("id" to NavArgument.Builder().setType(NavType.IntType).build())
+ mapOf("id" to intArgument())
)
assertWithMessage("Args should not be null")
.that(matchArgs)
diff --git a/navigation/navigation-common/src/androidTest/java/androidx/navigation/NavDestinationAndroidTest.kt b/navigation/navigation-common/src/androidTest/java/androidx/navigation/NavDestinationAndroidTest.kt
index 0aab743..41bbc79 100644
--- a/navigation/navigation-common/src/androidTest/java/androidx/navigation/NavDestinationAndroidTest.kt
+++ b/navigation/navigation-common/src/androidTest/java/androidx/navigation/NavDestinationAndroidTest.kt
@@ -18,6 +18,8 @@
import android.net.Uri
import android.os.Bundle
+import androidx.navigation.test.intArgument
+import androidx.navigation.test.stringArgument
import androidx.test.filters.SmallTest
import com.google.common.truth.Truth.assertThat
import com.google.common.truth.Truth.assertWithMessage
@@ -28,10 +30,7 @@
@Test
fun matchDeepLink() {
val destination = NoOpNavigator().createDestination()
- val idArgument = NavArgument.Builder()
- .setType(NavType.IntType)
- .build()
- destination.addArgument("id", idArgument)
+ destination.addArgument("id", intArgument())
destination.addDeepLink("www.example.com/users/{id}")
val match = destination.matchDeepLink(
@@ -52,10 +51,7 @@
destination.addDeepLink("www.example.com/users/index.html")
- val idArgument = NavArgument.Builder()
- .setType(NavType.StringType)
- .build()
- destination.addArgument("id", idArgument)
+ destination.addArgument("id", stringArgument())
destination.addDeepLink("www.example.com/users/{name}")
val match = destination.matchDeepLink(
@@ -89,16 +85,10 @@
fun matchDeepLinkBestMatch() {
val destination = NoOpNavigator().createDestination()
- val idArgument = NavArgument.Builder()
- .setType(NavType.IntType)
- .build()
- destination.addArgument("id", idArgument)
+ destination.addArgument("id", intArgument())
destination.addDeepLink("www.example.com/users/{id}")
- val postIdArgument = NavArgument.Builder()
- .setType(NavType.IntType)
- .build()
- destination.addArgument("postId", postIdArgument)
+ destination.addArgument("postId", intArgument())
destination.addDeepLink("www.example.com/users/{id}/posts/{postId}")
val match = destination.matchDeepLink(
@@ -131,10 +121,7 @@
@Test
fun testIsValidDeepLinkValidLinkPattern() {
val destination = NoOpNavigator().createDestination()
- val stringArgument = NavArgument.Builder()
- .setType(NavType.StringType)
- .build()
- destination.addArgument("testString", stringArgument)
+ destination.addArgument("testString", stringArgument())
destination.addDeepLink("android-app://androidx.navigation.test/{testString}")
val deepLink = Uri.parse("android-app://androidx.navigation.test/test")
destination.addDeepLink(deepLink.toString())
@@ -156,16 +143,8 @@
@Test
fun addInDefaultArgs() {
val destination = NoOpNavigator().createDestination()
- val stringArgument = NavArgument.Builder()
- .setType(NavType.StringType)
- .setDefaultValue("aaa")
- .build()
- val intArgument = NavArgument.Builder()
- .setType(NavType.IntType)
- .setDefaultValue(123)
- .build()
- destination.addArgument("stringArg", stringArgument)
- destination.addArgument("intArg", intArgument)
+ destination.addArgument("stringArg", stringArgument("aaa"))
+ destination.addArgument("intArg", intArgument(123))
val bundle = destination.addInDefaultArgs(Bundle().apply {
putString("stringArg", "bbb")
@@ -177,16 +156,8 @@
@Test(expected = IllegalArgumentException::class)
fun addInDefaultArgsWrong() {
val destination = NoOpNavigator().createDestination()
- val stringArgument = NavArgument.Builder()
- .setType(NavType.StringType)
- .setDefaultValue("aaa")
- .build()
- val intArgument = NavArgument.Builder()
- .setType(NavType.IntType)
- .setDefaultValue(123)
- .build()
- destination.addArgument("stringArg", stringArgument)
- destination.addArgument("intArg", intArgument)
+ destination.addArgument("stringArg", stringArgument("aaa"))
+ destination.addArgument("intArg", intArgument(123))
destination.addInDefaultArgs(Bundle().apply {
putInt("stringArg", 123)
diff --git a/navigation/navigation-common/src/androidTest/java/androidx/navigation/test/NavArgument.kt b/navigation/navigation-common/src/androidTest/java/androidx/navigation/test/NavArgument.kt
new file mode 100644
index 0000000..1446431
--- /dev/null
+++ b/navigation/navigation-common/src/androidTest/java/androidx/navigation/test/NavArgument.kt
@@ -0,0 +1,49 @@
+/*
+ * 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.navigation.test
+
+import androidx.navigation.NavArgument
+import androidx.navigation.NavType.IntType
+import androidx.navigation.NavType.StringType
+
+// region IntType
+fun intArgument() = NavArgument.Builder().setType(IntType).build()
+
+fun intArgument(
+ defaultValue: Int
+) = NavArgument.Builder().setType(IntType)
+ .setDefaultValue(defaultValue)
+ .build()
+// endregion
+
+// region StringType
+fun stringArgument(
+ isNullable: Boolean = false
+) = NavArgument.Builder().setType(StringType)
+ .setIsNullable(isNullable)
+ .build()
+
+fun stringArgument(
+ defaultValue: String
+) = NavArgument.Builder().setType(StringType)
+ .setDefaultValue(defaultValue)
+ .build()
+
+fun nullableStringArgument() = NavArgument.Builder().setType(StringType)
+ .setIsNullable(true)
+ .build()
+// endregion
diff --git a/navigation/navigation-fragment-ktx/src/main/java/androidx/navigation/NavGraphViewModelLazy.kt b/navigation/navigation-fragment-ktx/src/main/java/androidx/navigation/NavGraphViewModelLazy.kt
index 75e7122..51c7a2a 100644
--- a/navigation/navigation-fragment-ktx/src/main/java/androidx/navigation/NavGraphViewModelLazy.kt
+++ b/navigation/navigation-fragment-ktx/src/main/java/androidx/navigation/NavGraphViewModelLazy.kt
@@ -20,7 +20,6 @@
import androidx.annotation.MainThread
import androidx.fragment.app.Fragment
import androidx.fragment.app.createViewModelLazy
-import androidx.lifecycle.HasDefaultViewModelProviderFactory
import androidx.lifecycle.ViewModel
import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.ViewModelStore
@@ -39,7 +38,7 @@
* factory returned by it will be used to create [ViewModel]:
* ```
* class MyFragment : Fragment() {
- * val viewmodel: MainViewModel by navGraphViewModels(R.navigation.main) { myFactory }
+ * val viewmodel: MainViewModel by navGraphViewModels(R.id.main) { myFactory }
* }
* ```
*
@@ -53,14 +52,13 @@
@IdRes navGraphId: Int,
noinline factoryProducer: (() -> ViewModelProvider.Factory)? = null
): Lazy<VM> {
- val viewModelStoreOwner by lazy {
- findNavController().getViewModelStoreOwner(navGraphId)
+ val backStackEntry by lazy {
+ findNavController().getBackStackEntry(navGraphId)
}
val storeProducer: () -> ViewModelStore = {
- viewModelStoreOwner.viewModelStore
+ backStackEntry.viewModelStore
}
return createViewModelLazy(VM::class, storeProducer, {
- factoryProducer?.invoke() ?: (viewModelStoreOwner as HasDefaultViewModelProviderFactory)
- .defaultViewModelProviderFactory
+ factoryProducer?.invoke() ?: backStackEntry.defaultViewModelProviderFactory
})
}
\ No newline at end of file
diff --git a/navigation/navigation-fragment/src/androidTest/java/androidx/navigation/fragment/NavHostFragmentTest.kt b/navigation/navigation-fragment/src/androidTest/java/androidx/navigation/fragment/NavHostFragmentTest.kt
index ef65605..fbe2b50 100644
--- a/navigation/navigation-fragment/src/androidTest/java/androidx/navigation/fragment/NavHostFragmentTest.kt
+++ b/navigation/navigation-fragment/src/androidTest/java/androidx/navigation/fragment/NavHostFragmentTest.kt
@@ -98,4 +98,22 @@
.isSameInstanceAs(secondNavHostFragment.navController)
}
}
+
+ @Test
+ fun testFindNavControllerDynamicWithoutId() {
+ with(ActivityScenario.launch(EmptyActivity::class.java)) {
+ withActivity {
+ supportFragmentManager.beginTransaction()
+ .add(NavHostFragment(), "tag")
+ .commitNow()
+ }
+ val hostRootNavController = withActivity {
+ val navHostFragment = supportFragmentManager.findFragmentByTag("tag")!!
+ navHostFragment.requireView().findNavController()
+ }
+ assertWithMessage("NavController on the host's root view should be non-null")
+ .that(hostRootNavController)
+ .isNotNull()
+ }
+ }
}
diff --git a/navigation/navigation-fragment/src/main/java/androidx/navigation/fragment/NavHostFragment.java b/navigation/navigation-fragment/src/main/java/androidx/navigation/fragment/NavHostFragment.java
index a58f68e..87d4ff6 100644
--- a/navigation/navigation-fragment/src/main/java/androidx/navigation/fragment/NavHostFragment.java
+++ b/navigation/navigation-fragment/src/main/java/androidx/navigation/fragment/NavHostFragment.java
@@ -290,7 +290,8 @@
@Deprecated
@NonNull
protected Navigator<? extends FragmentNavigator.Destination> createFragmentNavigator() {
- return new FragmentNavigator(requireContext(), getChildFragmentManager(), getId());
+ return new FragmentNavigator(requireContext(), getChildFragmentManager(),
+ getContainerId());
}
@Nullable
@@ -302,10 +303,27 @@
// automatically), but this ensures that the View exists as part of this Fragment's View
// hierarchy in cases where the NavHostFragment is added programmatically as is required
// for child fragment transactions
- containerView.setId(getId());
+ containerView.setId(getContainerId());
return containerView;
}
+ /**
+ * We specifically can't use {@link View#NO_ID} as the container ID (as we use
+ * {@link androidx.fragment.app.FragmentTransaction#add(int, Fragment)} under the hood),
+ * so we need to make sure we return a valid ID when asked for the container ID.
+ *
+ * @return a valid ID to be used to contain child fragments
+ */
+ private int getContainerId() {
+ int id = getId();
+ if (id != 0 && id != View.NO_ID) {
+ return id;
+ }
+ // Fallback to using our own ID if this Fragment wasn't added via
+ // add(containerViewId, Fragment)
+ return R.id.nav_host_fragment_container;
+ }
+
@Override
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
diff --git a/navigation/navigation-fragment/src/main/res/values/ids.xml b/navigation/navigation-fragment/src/main/res/values/ids.xml
new file mode 100644
index 0000000..28a8854
--- /dev/null
+++ b/navigation/navigation-fragment/src/main/res/values/ids.xml
@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ 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.
+ -->
+<resources>
+ <item type="id" name="nav_host_fragment_container" />
+</resources>
diff --git a/navigation/navigation-runtime/api/2.2.0-alpha02.txt b/navigation/navigation-runtime/api/2.2.0-alpha02.txt
index 5db4937..a62443f 100644
--- a/navigation/navigation-runtime/api/2.2.0-alpha02.txt
+++ b/navigation/navigation-runtime/api/2.2.0-alpha02.txt
@@ -38,10 +38,20 @@
method public androidx.navigation.ActivityNavigator.Extras.Builder setActivityOptions(androidx.core.app.ActivityOptionsCompat);
}
+ public final class NavBackStackEntry implements androidx.lifecycle.HasDefaultViewModelProviderFactory androidx.lifecycle.LifecycleOwner androidx.savedstate.SavedStateRegistryOwner androidx.lifecycle.ViewModelStoreOwner {
+ method public android.os.Bundle? getArguments();
+ method public androidx.lifecycle.ViewModelProvider.Factory getDefaultViewModelProviderFactory();
+ method public androidx.navigation.NavDestination getDestination();
+ method public androidx.lifecycle.Lifecycle getLifecycle();
+ method public androidx.savedstate.SavedStateRegistry getSavedStateRegistry();
+ method public androidx.lifecycle.ViewModelStore getViewModelStore();
+ }
+
public class NavController {
ctor public NavController(android.content.Context);
method public void addOnDestinationChangedListener(androidx.navigation.NavController.OnDestinationChangedListener);
method public androidx.navigation.NavDeepLinkBuilder createDeepLink();
+ method public androidx.navigation.NavBackStackEntry getBackStackEntry(@IdRes int);
method public androidx.navigation.NavDestination? getCurrentDestination();
method public androidx.navigation.NavGraph getGraph();
method public androidx.navigation.NavInflater getNavInflater();
diff --git a/navigation/navigation-runtime/api/current.txt b/navigation/navigation-runtime/api/current.txt
index 5db4937..a62443f 100644
--- a/navigation/navigation-runtime/api/current.txt
+++ b/navigation/navigation-runtime/api/current.txt
@@ -38,10 +38,20 @@
method public androidx.navigation.ActivityNavigator.Extras.Builder setActivityOptions(androidx.core.app.ActivityOptionsCompat);
}
+ public final class NavBackStackEntry implements androidx.lifecycle.HasDefaultViewModelProviderFactory androidx.lifecycle.LifecycleOwner androidx.savedstate.SavedStateRegistryOwner androidx.lifecycle.ViewModelStoreOwner {
+ method public android.os.Bundle? getArguments();
+ method public androidx.lifecycle.ViewModelProvider.Factory getDefaultViewModelProviderFactory();
+ method public androidx.navigation.NavDestination getDestination();
+ method public androidx.lifecycle.Lifecycle getLifecycle();
+ method public androidx.savedstate.SavedStateRegistry getSavedStateRegistry();
+ method public androidx.lifecycle.ViewModelStore getViewModelStore();
+ }
+
public class NavController {
ctor public NavController(android.content.Context);
method public void addOnDestinationChangedListener(androidx.navigation.NavController.OnDestinationChangedListener);
method public androidx.navigation.NavDeepLinkBuilder createDeepLink();
+ method public androidx.navigation.NavBackStackEntry getBackStackEntry(@IdRes int);
method public androidx.navigation.NavDestination? getCurrentDestination();
method public androidx.navigation.NavGraph getGraph();
method public androidx.navigation.NavInflater getNavInflater();
diff --git a/navigation/navigation-runtime/api/restricted_2.2.0-alpha02.txt b/navigation/navigation-runtime/api/restricted_2.2.0-alpha02.txt
index 5db4937..a62443f 100644
--- a/navigation/navigation-runtime/api/restricted_2.2.0-alpha02.txt
+++ b/navigation/navigation-runtime/api/restricted_2.2.0-alpha02.txt
@@ -38,10 +38,20 @@
method public androidx.navigation.ActivityNavigator.Extras.Builder setActivityOptions(androidx.core.app.ActivityOptionsCompat);
}
+ public final class NavBackStackEntry implements androidx.lifecycle.HasDefaultViewModelProviderFactory androidx.lifecycle.LifecycleOwner androidx.savedstate.SavedStateRegistryOwner androidx.lifecycle.ViewModelStoreOwner {
+ method public android.os.Bundle? getArguments();
+ method public androidx.lifecycle.ViewModelProvider.Factory getDefaultViewModelProviderFactory();
+ method public androidx.navigation.NavDestination getDestination();
+ method public androidx.lifecycle.Lifecycle getLifecycle();
+ method public androidx.savedstate.SavedStateRegistry getSavedStateRegistry();
+ method public androidx.lifecycle.ViewModelStore getViewModelStore();
+ }
+
public class NavController {
ctor public NavController(android.content.Context);
method public void addOnDestinationChangedListener(androidx.navigation.NavController.OnDestinationChangedListener);
method public androidx.navigation.NavDeepLinkBuilder createDeepLink();
+ method public androidx.navigation.NavBackStackEntry getBackStackEntry(@IdRes int);
method public androidx.navigation.NavDestination? getCurrentDestination();
method public androidx.navigation.NavGraph getGraph();
method public androidx.navigation.NavInflater getNavInflater();
diff --git a/navigation/navigation-runtime/api/restricted_current.txt b/navigation/navigation-runtime/api/restricted_current.txt
index 5db4937..a62443f 100644
--- a/navigation/navigation-runtime/api/restricted_current.txt
+++ b/navigation/navigation-runtime/api/restricted_current.txt
@@ -38,10 +38,20 @@
method public androidx.navigation.ActivityNavigator.Extras.Builder setActivityOptions(androidx.core.app.ActivityOptionsCompat);
}
+ public final class NavBackStackEntry implements androidx.lifecycle.HasDefaultViewModelProviderFactory androidx.lifecycle.LifecycleOwner androidx.savedstate.SavedStateRegistryOwner androidx.lifecycle.ViewModelStoreOwner {
+ method public android.os.Bundle? getArguments();
+ method public androidx.lifecycle.ViewModelProvider.Factory getDefaultViewModelProviderFactory();
+ method public androidx.navigation.NavDestination getDestination();
+ method public androidx.lifecycle.Lifecycle getLifecycle();
+ method public androidx.savedstate.SavedStateRegistry getSavedStateRegistry();
+ method public androidx.lifecycle.ViewModelStore getViewModelStore();
+ }
+
public class NavController {
ctor public NavController(android.content.Context);
method public void addOnDestinationChangedListener(androidx.navigation.NavController.OnDestinationChangedListener);
method public androidx.navigation.NavDeepLinkBuilder createDeepLink();
+ method public androidx.navigation.NavBackStackEntry getBackStackEntry(@IdRes int);
method public androidx.navigation.NavDestination? getCurrentDestination();
method public androidx.navigation.NavGraph getGraph();
method public androidx.navigation.NavInflater getNavInflater();
diff --git a/navigation/navigation-runtime/src/androidTest/java/androidx/navigation/NavBackStackEntryLifecycleTest.kt b/navigation/navigation-runtime/src/androidTest/java/androidx/navigation/NavBackStackEntryLifecycleTest.kt
index 85e6f4a..88847fa 100644
--- a/navigation/navigation-runtime/src/androidTest/java/androidx/navigation/NavBackStackEntryLifecycleTest.kt
+++ b/navigation/navigation-runtime/src/androidTest/java/androidx/navigation/NavBackStackEntryLifecycleTest.kt
@@ -58,9 +58,9 @@
assertWithMessage("The parent graph should be resumed when its child is resumed")
.that(graphBackStackEntry.lifecycle.currentState)
.isEqualTo(Lifecycle.State.RESUMED)
- val startBackStackEntry = navController.findBackStackEntry(R.id.start_test)
+ val startBackStackEntry = navController.getBackStackEntry(R.id.start_test)
assertWithMessage("The start destination should be resumed")
- .that(startBackStackEntry!!.lifecycle.currentState)
+ .that(startBackStackEntry.lifecycle.currentState)
.isEqualTo(Lifecycle.State.RESUMED)
navController.navigate(R.id.second_test)
@@ -71,9 +71,9 @@
assertWithMessage("The start destination should be set back to created after you navigate")
.that(startBackStackEntry.lifecycle.currentState)
.isEqualTo(Lifecycle.State.CREATED)
- val secondBackStackEntry = navController.findBackStackEntry(R.id.second_test)
+ val secondBackStackEntry = navController.getBackStackEntry(R.id.second_test)
assertWithMessage("The new destination should be resumed")
- .that(secondBackStackEntry!!.lifecycle.currentState)
+ .that(secondBackStackEntry.lifecycle.currentState)
.isEqualTo(Lifecycle.State.RESUMED)
navController.popBackStack()
@@ -119,9 +119,9 @@
assertWithMessage("The parent graph should be resumed when its child is resumed")
.that(graphBackStackEntry.lifecycle.currentState)
.isEqualTo(Lifecycle.State.RESUMED)
- val startBackStackEntry = navController.findBackStackEntry(R.id.start_test)
+ val startBackStackEntry = navController.getBackStackEntry(R.id.start_test)
assertWithMessage("The start destination should be resumed")
- .that(startBackStackEntry!!.lifecycle.currentState)
+ .that(startBackStackEntry.lifecycle.currentState)
.isEqualTo(Lifecycle.State.RESUMED)
navController.navigate(R.id.second_test)
@@ -132,9 +132,9 @@
assertWithMessage("The start destination should be started when a FloatingWindow is open")
.that(startBackStackEntry.lifecycle.currentState)
.isEqualTo(Lifecycle.State.STARTED)
- val secondBackStackEntry = navController.findBackStackEntry(R.id.second_test)
+ val secondBackStackEntry = navController.getBackStackEntry(R.id.second_test)
assertWithMessage("The new destination should be resumed")
- .that(secondBackStackEntry!!.lifecycle.currentState)
+ .that(secondBackStackEntry.lifecycle.currentState)
.isEqualTo(Lifecycle.State.RESUMED)
navController.popBackStack()
@@ -186,9 +186,9 @@
assertWithMessage("The nested graph should be resumed when its child is resumed")
.that(nestedGraphBackStackEntry.lifecycle.currentState)
.isEqualTo(Lifecycle.State.RESUMED)
- val nestedBackStackEntry = navController.findBackStackEntry(R.id.nested_test)
+ val nestedBackStackEntry = navController.getBackStackEntry(R.id.nested_test)
assertWithMessage("The nested start destination should be resumed")
- .that(nestedBackStackEntry!!.lifecycle.currentState)
+ .that(nestedBackStackEntry.lifecycle.currentState)
.isEqualTo(Lifecycle.State.RESUMED)
navController.navigate(R.id.second_test)
@@ -202,9 +202,9 @@
assertWithMessage("The nested start destination should be stopped after navigate")
.that(nestedBackStackEntry.lifecycle.currentState)
.isEqualTo(Lifecycle.State.CREATED)
- val secondBackStackEntry = navController.findBackStackEntry(R.id.second_test)
+ val secondBackStackEntry = navController.getBackStackEntry(R.id.second_test)
assertWithMessage("The new destination should be resumed")
- .that(secondBackStackEntry!!.lifecycle.currentState)
+ .that(secondBackStackEntry.lifecycle.currentState)
.isEqualTo(Lifecycle.State.RESUMED)
navController.popBackStack()
@@ -250,9 +250,9 @@
assertWithMessage("The nested graph should be resumed when its child is resumed")
.that(nestedGraphBackStackEntry.lifecycle.currentState)
.isEqualTo(Lifecycle.State.RESUMED)
- val nestedBackStackEntry = navController.findBackStackEntry(R.id.nested_test)
+ val nestedBackStackEntry = navController.getBackStackEntry(R.id.nested_test)
assertWithMessage("The nested start destination should be resumed")
- .that(nestedBackStackEntry!!.lifecycle.currentState)
+ .that(nestedBackStackEntry.lifecycle.currentState)
.isEqualTo(Lifecycle.State.RESUMED)
navController.navigate(R.id.second_test)
@@ -267,9 +267,9 @@
"FloatingWindow is open")
.that(nestedBackStackEntry.lifecycle.currentState)
.isEqualTo(Lifecycle.State.STARTED)
- val secondBackStackEntry = navController.findBackStackEntry(R.id.second_test)
+ val secondBackStackEntry = navController.getBackStackEntry(R.id.second_test)
assertWithMessage("The new destination should be resumed")
- .that(secondBackStackEntry!!.lifecycle.currentState)
+ .that(secondBackStackEntry.lifecycle.currentState)
.isEqualTo(Lifecycle.State.RESUMED)
navController.popBackStack()
@@ -312,9 +312,9 @@
val nestedGraphBackStackEntry = navController.getBackStackEntry(R.id.nested)
val nestedGraphObserver = mock(LifecycleEventObserver::class.java)
nestedGraphBackStackEntry.lifecycle.addObserver(nestedGraphObserver)
- val nestedBackStackEntry = navController.findBackStackEntry(R.id.nested_test)
+ val nestedBackStackEntry = navController.getBackStackEntry(R.id.nested_test)
val nestedObserver = mock(LifecycleEventObserver::class.java)
- nestedBackStackEntry!!.lifecycle.addObserver(nestedObserver)
+ nestedBackStackEntry.lifecycle.addObserver(nestedObserver)
val inOrder = inOrder(graphObserver, nestedGraphObserver, nestedObserver)
inOrder.verify(graphObserver).onStateChanged(
graphBackStackEntry, Lifecycle.Event.ON_CREATE)
@@ -389,9 +389,9 @@
val nestedGraphBackStackEntry = navController.getBackStackEntry(R.id.nested)
val nestedGraphObserver = mock(LifecycleEventObserver::class.java)
nestedGraphBackStackEntry.lifecycle.addObserver(nestedGraphObserver)
- val nestedBackStackEntry = navController.findBackStackEntry(R.id.nested_test)
+ val nestedBackStackEntry = navController.getBackStackEntry(R.id.nested_test)
val nestedObserver = mock(LifecycleEventObserver::class.java)
- nestedBackStackEntry!!.lifecycle.addObserver(nestedObserver)
+ nestedBackStackEntry.lifecycle.addObserver(nestedObserver)
val inOrder = inOrder(graphObserver, nestedGraphObserver, nestedObserver)
inOrder.verify(graphObserver).onStateChanged(
graphBackStackEntry, Lifecycle.Event.ON_CREATE)
@@ -459,9 +459,9 @@
assertWithMessage("The nested graph should be resumed when its child is resumed")
.that(nestedGraphBackStackEntry.lifecycle.currentState)
.isEqualTo(Lifecycle.State.RESUMED)
- val nestedBackStackEntry = navController.findBackStackEntry(R.id.nested_test)
+ val nestedBackStackEntry = navController.getBackStackEntry(R.id.nested_test)
assertWithMessage("The nested start destination should be resumed")
- .that(nestedBackStackEntry!!.lifecycle.currentState)
+ .that(nestedBackStackEntry.lifecycle.currentState)
.isEqualTo(Lifecycle.State.RESUMED)
navController.navigate(R.id.second_test)
@@ -475,9 +475,9 @@
assertWithMessage("The nested start destination should be stopped after navigate")
.that(nestedBackStackEntry.lifecycle.currentState)
.isEqualTo(Lifecycle.State.CREATED)
- val secondBackStackEntry = navController.findBackStackEntry(R.id.second_test)
+ val secondBackStackEntry = navController.getBackStackEntry(R.id.second_test)
assertWithMessage("The new destination should be resumed")
- .that(secondBackStackEntry!!.lifecycle.currentState)
+ .that(secondBackStackEntry.lifecycle.currentState)
.isEqualTo(Lifecycle.State.RESUMED)
// Navigate to a new instance of the nested graph
@@ -499,9 +499,9 @@
assertWithMessage("The new nested graph should be resumed when its child is resumed")
.that(newNestedGraphBackStackEntry.lifecycle.currentState)
.isEqualTo(Lifecycle.State.RESUMED)
- val newNestedBackStackEntry = navController.findBackStackEntry(R.id.nested_test)
+ val newNestedBackStackEntry = navController.getBackStackEntry(R.id.nested_test)
assertWithMessage("The new nested start destination should be resumed")
- .that(newNestedBackStackEntry!!.lifecycle.currentState)
+ .that(newNestedBackStackEntry.lifecycle.currentState)
.isEqualTo(Lifecycle.State.RESUMED)
}
@@ -534,9 +534,9 @@
assertWithMessage("The nested graph should be resumed when its child is resumed")
.that(nestedGraphBackStackEntry.lifecycle.currentState)
.isEqualTo(Lifecycle.State.RESUMED)
- val nestedBackStackEntry = navController.findBackStackEntry(R.id.nested_test)
+ val nestedBackStackEntry = navController.getBackStackEntry(R.id.nested_test)
assertWithMessage("The nested start destination should be resumed")
- .that(nestedBackStackEntry!!.lifecycle.currentState)
+ .that(nestedBackStackEntry.lifecycle.currentState)
.isEqualTo(Lifecycle.State.RESUMED)
navController.navigate(R.id.second_test)
@@ -550,9 +550,9 @@
assertWithMessage("The nested start destination should be stopped after navigate")
.that(nestedBackStackEntry.lifecycle.currentState)
.isEqualTo(Lifecycle.State.CREATED)
- val secondBackStackEntry = navController.findBackStackEntry(R.id.second_test)
+ val secondBackStackEntry = navController.getBackStackEntry(R.id.second_test)
assertWithMessage("The new destination should be resumed")
- .that(secondBackStackEntry!!.lifecycle.currentState)
+ .that(secondBackStackEntry.lifecycle.currentState)
.isEqualTo(Lifecycle.State.RESUMED)
// Navigate to a new instance of the nested graph using a deep link to a dialog
@@ -574,9 +574,9 @@
assertWithMessage("The new nested graph should be resumed when its child is resumed")
.that(newNestedGraphBackStackEntry.lifecycle.currentState)
.isEqualTo(Lifecycle.State.RESUMED)
- val newNestedBackStackEntry = navController.findBackStackEntry(R.id.nested_second_test)
+ val newNestedBackStackEntry = navController.getBackStackEntry(R.id.nested_second_test)
assertWithMessage("The new nested start destination should be resumed")
- .that(newNestedBackStackEntry!!.lifecycle.currentState)
+ .that(newNestedBackStackEntry.lifecycle.currentState)
.isEqualTo(Lifecycle.State.RESUMED)
}
diff --git a/navigation/navigation-runtime/src/androidTest/java/androidx/navigation/NavBackStackEntryTest.kt b/navigation/navigation-runtime/src/androidTest/java/androidx/navigation/NavBackStackEntryTest.kt
index 5299844..ab84f8c 100644
--- a/navigation/navigation-runtime/src/androidTest/java/androidx/navigation/NavBackStackEntryTest.kt
+++ b/navigation/navigation-runtime/src/androidTest/java/androidx/navigation/NavBackStackEntryTest.kt
@@ -150,7 +150,7 @@
} catch (e: IllegalArgumentException) {
assertThat(e)
.hasMessageThat().contains(
- "No NavGraph with ID $navGraphId is on the NavController's back stack"
+ "No destination with ID $navGraphId is on the NavController's back stack"
)
}
}
diff --git a/navigation/navigation-runtime/src/main/java/androidx/navigation/NavBackStackEntry.java b/navigation/navigation-runtime/src/main/java/androidx/navigation/NavBackStackEntry.java
index 8614bc8..8a071ab 100644
--- a/navigation/navigation-runtime/src/main/java/androidx/navigation/NavBackStackEntry.java
+++ b/navigation/navigation-runtime/src/main/java/androidx/navigation/NavBackStackEntry.java
@@ -43,7 +43,7 @@
* destination is popped off the back stack, the lifecycle will be destroyed, state
* will no longer be saved, and ViewModels will be cleared.
*/
-final class NavBackStackEntry implements
+public final class NavBackStackEntry implements
LifecycleOwner,
ViewModelStoreOwner, HasDefaultViewModelProviderFactory,
SavedStateRegistryOwner {
diff --git a/navigation/navigation-runtime/src/main/java/androidx/navigation/NavController.java b/navigation/navigation-runtime/src/main/java/androidx/navigation/NavController.java
index 45920e0..4ab77ad 100644
--- a/navigation/navigation-runtime/src/main/java/androidx/navigation/NavController.java
+++ b/navigation/navigation-runtime/src/main/java/androidx/navigation/NavController.java
@@ -1139,34 +1139,22 @@
throw new IllegalStateException("You must call setViewModelStore() before calling "
+ "getViewModelStoreOwner().");
}
- return getBackStackEntry(navGraphId);
- }
-
- /**
- * Gets the {@link NavBackStackEntry} for a NavGraph.
- *
- * @param navGraphId ID of a NavGraph that exists on the back stack
- * @throws IllegalArgumentException if the NavGraph is not on the back stack
- */
- @NonNull
- NavBackStackEntry getBackStackEntry(@IdRes int navGraphId) {
- NavBackStackEntry lastFromBackStack = findBackStackEntry(navGraphId);
- if (lastFromBackStack == null
- || !(lastFromBackStack.getDestination() instanceof NavGraph)) {
- throw new IllegalArgumentException("No NavGraph with ID " + navGraphId + " is on the "
- + "NavController's back stack");
+ NavBackStackEntry lastFromBackStack = getBackStackEntry(navGraphId);
+ if (!(lastFromBackStack.getDestination() instanceof NavGraph)) {
+ throw new IllegalArgumentException("No NavGraph with ID " + navGraphId
+ + " is on the NavController's back stack");
}
return lastFromBackStack;
}
/**
- * Find the topmost {@link NavBackStackEntry} for a destination id.
+ * Gets the topmost {@link NavBackStackEntry} for a destination id.
*
* @param destinationId ID of a destination that exists on the back stack
* @throws IllegalArgumentException if the destination is not on the back stack
*/
- @Nullable
- NavBackStackEntry findBackStackEntry(@IdRes int destinationId) {
+ @NonNull
+ public NavBackStackEntry getBackStackEntry(@IdRes int destinationId) {
NavBackStackEntry lastFromBackStack = null;
Iterator<NavBackStackEntry> iterator = mBackStack.descendingIterator();
while (iterator.hasNext()) {
@@ -1177,6 +1165,10 @@
break;
}
}
+ if (lastFromBackStack == null) {
+ throw new IllegalArgumentException("No destination with ID " + destinationId
+ + " is on the NavController's back stack");
+ }
return lastFromBackStack;
}
}
diff --git a/paging/common/api/3.0.0-alpha01.txt b/paging/common/api/3.0.0-alpha01.txt
index fc77d4c..4cb7b1d 100644
--- a/paging/common/api/3.0.0-alpha01.txt
+++ b/paging/common/api/3.0.0-alpha01.txt
@@ -135,23 +135,28 @@
ctor public PageKeyedDataSourceKt();
}
+ public enum PageLoadType {
+ enum_constant public static final androidx.paging.PageLoadType END;
+ enum_constant public static final androidx.paging.PageLoadType REFRESH;
+ enum_constant public static final androidx.paging.PageLoadType START;
+ }
+
public abstract class PagedList<T> extends java.util.AbstractList<T> {
method public void addWeakCallback(java.util.List<? extends T>? previousSnapshot, androidx.paging.PagedList.Callback callback);
- method public void addWeakLoadStateListener(kotlin.jvm.functions.Function3<? super androidx.paging.PagedList.LoadType,? super androidx.paging.PagedList.LoadState,? super java.lang.Throwable,kotlin.Unit> listener);
+ method public void addWeakLoadStateListener(kotlin.jvm.functions.Function2<? super androidx.paging.PageLoadType,? super androidx.paging.PagedList.LoadState,kotlin.Unit> listener);
method public abstract void detach();
method public T? get(int index);
method public androidx.paging.PagedList.Config getConfig();
method @Deprecated public final androidx.paging.DataSource<?,T> getDataSource();
method public abstract Object? getLastKey();
method public int getLoadedCount();
- method public final androidx.paging.PagedSource<?,T> getPagedSource();
method public int getPositionOffset();
method public int getSize();
method public abstract boolean isDetached();
method public boolean isImmutable();
method public void loadAround(int index);
method public void removeWeakCallback(androidx.paging.PagedList.Callback callback);
- method public void removeWeakLoadStateListener(kotlin.jvm.functions.Function3<? super androidx.paging.PagedList.LoadType,? super androidx.paging.PagedList.LoadState,? super java.lang.Throwable,kotlin.Unit> listener);
+ method public void removeWeakLoadStateListener(kotlin.jvm.functions.Function2<? super androidx.paging.PageLoadType,? super androidx.paging.PagedList.LoadState,kotlin.Unit> listener);
method public void retry();
method public java.util.List<T> snapshot();
property public androidx.paging.PagedList.Config config;
@@ -160,7 +165,6 @@
property public boolean isImmutable;
property public abstract Object? lastKey;
property public int loadedCount;
- property public final androidx.paging.PagedSource<?,T> pagedSource;
property public int positionOffset;
property public int size;
}
@@ -214,18 +218,28 @@
method public androidx.paging.PagedList.Config.Builder setPrefetchDistance(@IntRange(from=0) int prefetchDistance);
}
- public enum PagedList.LoadState {
- enum_constant public static final androidx.paging.PagedList.LoadState DONE;
- enum_constant public static final androidx.paging.PagedList.LoadState ERROR;
- enum_constant public static final androidx.paging.PagedList.LoadState IDLE;
- enum_constant public static final androidx.paging.PagedList.LoadState LOADING;
- enum_constant public static final androidx.paging.PagedList.LoadState RETRYABLE_ERROR;
+ public abstract static sealed class PagedList.LoadState {
}
- public enum PagedList.LoadType {
- enum_constant public static final androidx.paging.PagedList.LoadType END;
- enum_constant public static final androidx.paging.PagedList.LoadType REFRESH;
- enum_constant public static final androidx.paging.PagedList.LoadType START;
+ public static final class PagedList.LoadState.Done extends androidx.paging.PagedList.LoadState {
+ field public static final androidx.paging.PagedList.LoadState.Done! INSTANCE;
+ }
+
+ public static final class PagedList.LoadState.Error extends androidx.paging.PagedList.LoadState {
+ ctor public PagedList.LoadState.Error(Throwable error, boolean retryable);
+ method public Throwable component1();
+ method public boolean component2();
+ method public androidx.paging.PagedList.LoadState.Error copy(Throwable error, boolean retryable);
+ method public Throwable getError();
+ method public boolean getRetryable();
+ }
+
+ public static final class PagedList.LoadState.Idle extends androidx.paging.PagedList.LoadState {
+ field public static final androidx.paging.PagedList.LoadState.Idle! INSTANCE;
+ }
+
+ public static final class PagedList.LoadState.Loading extends androidx.paging.PagedList.LoadState {
+ field public static final androidx.paging.PagedList.LoadState.Loading! INSTANCE;
}
public final class PagedListConfigKt {
@@ -268,16 +282,16 @@
}
public static final class PagedSource.LoadParams<Key> {
- ctor public PagedSource.LoadParams(androidx.paging.PagedSource.LoadType loadType, Key? key, int loadSize, boolean placeholdersEnabled, int pageSize);
- method public androidx.paging.PagedSource.LoadType component1();
+ ctor public PagedSource.LoadParams(androidx.paging.PageLoadType loadType, Key? key, int loadSize, boolean placeholdersEnabled, int pageSize);
+ method public androidx.paging.PageLoadType component1();
method public Key? component2();
method public int component3();
method public boolean component4();
method public int component5();
- method public androidx.paging.PagedSource.LoadParams<Key> copy(androidx.paging.PagedSource.LoadType loadType, Key? key, int loadSize, boolean placeholdersEnabled, int pageSize);
+ method public androidx.paging.PagedSource.LoadParams<Key> copy(androidx.paging.PageLoadType loadType, Key? key, int loadSize, boolean placeholdersEnabled, int pageSize);
method public Key? getKey();
method public int getLoadSize();
- method public androidx.paging.PagedSource.LoadType getLoadType();
+ method public androidx.paging.PageLoadType getLoadType();
method public int getPageSize();
method public boolean getPlaceholdersEnabled();
}
@@ -302,12 +316,6 @@
public static final class PagedSource.LoadResult.Companion {
}
- public enum PagedSource.LoadType {
- enum_constant public static final androidx.paging.PagedSource.LoadType END;
- enum_constant public static final androidx.paging.PagedSource.LoadType INITIAL;
- enum_constant public static final androidx.paging.PagedSource.LoadType START;
- }
-
public final class PagedSourceKt {
ctor public PagedSourceKt();
}
diff --git a/paging/common/api/api_lint.ignore b/paging/common/api/api_lint.ignore
index 455d188..89cfe45 100644
--- a/paging/common/api/api_lint.ignore
+++ b/paging/common/api/api_lint.ignore
@@ -23,6 +23,12 @@
Parameter p references hidden type class androidx.paging.PositionalDataSource.InitialResult.
+MissingNullability: androidx.paging.PagedList.LoadState.Done#INSTANCE:
+ Missing nullability on field `INSTANCE` in class `class androidx.paging.PagedList.LoadState.Done`
+MissingNullability: androidx.paging.PagedList.LoadState.Idle#INSTANCE:
+ Missing nullability on field `INSTANCE` in class `class androidx.paging.PagedList.LoadState.Idle`
+MissingNullability: androidx.paging.PagedList.LoadState.Loading#INSTANCE:
+ Missing nullability on field `INSTANCE` in class `class androidx.paging.PagedList.LoadState.Loading`
MissingNullability: androidx.paging.PagedSource.KeyProvider.Positional#INSTANCE:
Missing nullability on field `INSTANCE` in class `class androidx.paging.PagedSource.KeyProvider.Positional`
MissingNullability: androidx.paging.PagedSource.LoadResult#Companion:
diff --git a/paging/common/api/current.txt b/paging/common/api/current.txt
index fc77d4c..4cb7b1d 100644
--- a/paging/common/api/current.txt
+++ b/paging/common/api/current.txt
@@ -135,23 +135,28 @@
ctor public PageKeyedDataSourceKt();
}
+ public enum PageLoadType {
+ enum_constant public static final androidx.paging.PageLoadType END;
+ enum_constant public static final androidx.paging.PageLoadType REFRESH;
+ enum_constant public static final androidx.paging.PageLoadType START;
+ }
+
public abstract class PagedList<T> extends java.util.AbstractList<T> {
method public void addWeakCallback(java.util.List<? extends T>? previousSnapshot, androidx.paging.PagedList.Callback callback);
- method public void addWeakLoadStateListener(kotlin.jvm.functions.Function3<? super androidx.paging.PagedList.LoadType,? super androidx.paging.PagedList.LoadState,? super java.lang.Throwable,kotlin.Unit> listener);
+ method public void addWeakLoadStateListener(kotlin.jvm.functions.Function2<? super androidx.paging.PageLoadType,? super androidx.paging.PagedList.LoadState,kotlin.Unit> listener);
method public abstract void detach();
method public T? get(int index);
method public androidx.paging.PagedList.Config getConfig();
method @Deprecated public final androidx.paging.DataSource<?,T> getDataSource();
method public abstract Object? getLastKey();
method public int getLoadedCount();
- method public final androidx.paging.PagedSource<?,T> getPagedSource();
method public int getPositionOffset();
method public int getSize();
method public abstract boolean isDetached();
method public boolean isImmutable();
method public void loadAround(int index);
method public void removeWeakCallback(androidx.paging.PagedList.Callback callback);
- method public void removeWeakLoadStateListener(kotlin.jvm.functions.Function3<? super androidx.paging.PagedList.LoadType,? super androidx.paging.PagedList.LoadState,? super java.lang.Throwable,kotlin.Unit> listener);
+ method public void removeWeakLoadStateListener(kotlin.jvm.functions.Function2<? super androidx.paging.PageLoadType,? super androidx.paging.PagedList.LoadState,kotlin.Unit> listener);
method public void retry();
method public java.util.List<T> snapshot();
property public androidx.paging.PagedList.Config config;
@@ -160,7 +165,6 @@
property public boolean isImmutable;
property public abstract Object? lastKey;
property public int loadedCount;
- property public final androidx.paging.PagedSource<?,T> pagedSource;
property public int positionOffset;
property public int size;
}
@@ -214,18 +218,28 @@
method public androidx.paging.PagedList.Config.Builder setPrefetchDistance(@IntRange(from=0) int prefetchDistance);
}
- public enum PagedList.LoadState {
- enum_constant public static final androidx.paging.PagedList.LoadState DONE;
- enum_constant public static final androidx.paging.PagedList.LoadState ERROR;
- enum_constant public static final androidx.paging.PagedList.LoadState IDLE;
- enum_constant public static final androidx.paging.PagedList.LoadState LOADING;
- enum_constant public static final androidx.paging.PagedList.LoadState RETRYABLE_ERROR;
+ public abstract static sealed class PagedList.LoadState {
}
- public enum PagedList.LoadType {
- enum_constant public static final androidx.paging.PagedList.LoadType END;
- enum_constant public static final androidx.paging.PagedList.LoadType REFRESH;
- enum_constant public static final androidx.paging.PagedList.LoadType START;
+ public static final class PagedList.LoadState.Done extends androidx.paging.PagedList.LoadState {
+ field public static final androidx.paging.PagedList.LoadState.Done! INSTANCE;
+ }
+
+ public static final class PagedList.LoadState.Error extends androidx.paging.PagedList.LoadState {
+ ctor public PagedList.LoadState.Error(Throwable error, boolean retryable);
+ method public Throwable component1();
+ method public boolean component2();
+ method public androidx.paging.PagedList.LoadState.Error copy(Throwable error, boolean retryable);
+ method public Throwable getError();
+ method public boolean getRetryable();
+ }
+
+ public static final class PagedList.LoadState.Idle extends androidx.paging.PagedList.LoadState {
+ field public static final androidx.paging.PagedList.LoadState.Idle! INSTANCE;
+ }
+
+ public static final class PagedList.LoadState.Loading extends androidx.paging.PagedList.LoadState {
+ field public static final androidx.paging.PagedList.LoadState.Loading! INSTANCE;
}
public final class PagedListConfigKt {
@@ -268,16 +282,16 @@
}
public static final class PagedSource.LoadParams<Key> {
- ctor public PagedSource.LoadParams(androidx.paging.PagedSource.LoadType loadType, Key? key, int loadSize, boolean placeholdersEnabled, int pageSize);
- method public androidx.paging.PagedSource.LoadType component1();
+ ctor public PagedSource.LoadParams(androidx.paging.PageLoadType loadType, Key? key, int loadSize, boolean placeholdersEnabled, int pageSize);
+ method public androidx.paging.PageLoadType component1();
method public Key? component2();
method public int component3();
method public boolean component4();
method public int component5();
- method public androidx.paging.PagedSource.LoadParams<Key> copy(androidx.paging.PagedSource.LoadType loadType, Key? key, int loadSize, boolean placeholdersEnabled, int pageSize);
+ method public androidx.paging.PagedSource.LoadParams<Key> copy(androidx.paging.PageLoadType loadType, Key? key, int loadSize, boolean placeholdersEnabled, int pageSize);
method public Key? getKey();
method public int getLoadSize();
- method public androidx.paging.PagedSource.LoadType getLoadType();
+ method public androidx.paging.PageLoadType getLoadType();
method public int getPageSize();
method public boolean getPlaceholdersEnabled();
}
@@ -302,12 +316,6 @@
public static final class PagedSource.LoadResult.Companion {
}
- public enum PagedSource.LoadType {
- enum_constant public static final androidx.paging.PagedSource.LoadType END;
- enum_constant public static final androidx.paging.PagedSource.LoadType INITIAL;
- enum_constant public static final androidx.paging.PagedSource.LoadType START;
- }
-
public final class PagedSourceKt {
ctor public PagedSourceKt();
}
diff --git a/paging/common/api/restricted_3.0.0-alpha01.txt b/paging/common/api/restricted_3.0.0-alpha01.txt
index b3bd226..976e9a3 100644
--- a/paging/common/api/restricted_3.0.0-alpha01.txt
+++ b/paging/common/api/restricted_3.0.0-alpha01.txt
@@ -44,7 +44,6 @@
method @AnyThread public void onInvalidated();
}
-
public static final class DataSource.Params<K> {
method public int getInitialLoadSize();
method public K? getKey();
@@ -139,10 +138,16 @@
ctor public PageKeyedDataSourceKt();
}
+ public enum PageLoadType {
+ enum_constant public static final androidx.paging.PageLoadType END;
+ enum_constant public static final androidx.paging.PageLoadType REFRESH;
+ enum_constant public static final androidx.paging.PageLoadType START;
+ }
+
public abstract class PagedList<T> extends java.util.AbstractList<T> {
ctor @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP) public PagedList(kotlinx.coroutines.CoroutineScope coroutineScope, androidx.paging.PagedSource<?,T> pagedSource, androidx.paging.PagedStorage<T> storage, kotlinx.coroutines.CoroutineDispatcher notifyDispatcher, kotlinx.coroutines.CoroutineDispatcher backgroundDispatcher, androidx.paging.PagedList.BoundaryCallback<T>? boundaryCallback, androidx.paging.PagedList.Config config);
method public void addWeakCallback(java.util.List<? extends T>? previousSnapshot, androidx.paging.PagedList.Callback callback);
- method public void addWeakLoadStateListener(kotlin.jvm.functions.Function3<? super androidx.paging.PagedList.LoadType,? super androidx.paging.PagedList.LoadState,? super java.lang.Throwable,kotlin.Unit> listener);
+ method public void addWeakLoadStateListener(kotlin.jvm.functions.Function2<? super androidx.paging.PageLoadType,? super androidx.paging.PagedList.LoadState,kotlin.Unit> listener);
method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP) public static final suspend <K, T> Object create(androidx.paging.PagedSource<K,T> p, kotlinx.coroutines.CoroutineScope pagedSource, kotlinx.coroutines.CoroutineDispatcher coroutineScope, kotlinx.coroutines.CoroutineDispatcher notifyDispatcher, kotlinx.coroutines.CoroutineDispatcher fetchDispatcher, androidx.paging.PagedList.BoundaryCallback<T>? initialFetchDispatcher, androidx.paging.PagedList.Config boundaryCallback, K? config, kotlin.coroutines.Continuation<? super androidx.paging.PagedList<T>> key);
method public abstract void detach();
method public T? get(int index);
@@ -150,7 +155,6 @@
method @Deprecated public final androidx.paging.DataSource<?,T> getDataSource();
method public abstract Object? getLastKey();
method public int getLoadedCount();
- method public final androidx.paging.PagedSource<?,T> getPagedSource();
method public int getPositionOffset();
method public int getSize();
method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP) public final androidx.paging.PagedStorage<T> getStorage();
@@ -158,9 +162,9 @@
method public boolean isImmutable();
method public void loadAround(int index);
method public void removeWeakCallback(androidx.paging.PagedList.Callback callback);
- method public void removeWeakLoadStateListener(kotlin.jvm.functions.Function3<? super androidx.paging.PagedList.LoadType,? super androidx.paging.PagedList.LoadState,? super java.lang.Throwable,kotlin.Unit> listener);
+ method public void removeWeakLoadStateListener(kotlin.jvm.functions.Function2<? super androidx.paging.PageLoadType,? super androidx.paging.PagedList.LoadState,kotlin.Unit> listener);
method public void retry();
- method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP) public void setInitialLoadState(androidx.paging.PagedList.LoadState loadState, Throwable? error);
+ method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP) public void setInitialLoadState(androidx.paging.PageLoadType loadType, androidx.paging.PagedList.LoadState loadState);
method public java.util.List<T> snapshot();
property public androidx.paging.PagedList.Config config;
property @Deprecated public final androidx.paging.DataSource<?,T> dataSource;
@@ -168,7 +172,6 @@
property public boolean isImmutable;
property public abstract Object? lastKey;
property public int loadedCount;
- property public final androidx.paging.PagedSource<?,T> pagedSource;
property public int positionOffset;
property public int size;
}
@@ -226,30 +229,43 @@
method public androidx.paging.PagedList.Config.Builder setPrefetchDistance(@IntRange(from=0) int prefetchDistance);
}
- public enum PagedList.LoadState {
- enum_constant public static final androidx.paging.PagedList.LoadState DONE;
- enum_constant public static final androidx.paging.PagedList.LoadState ERROR;
- enum_constant public static final androidx.paging.PagedList.LoadState IDLE;
- enum_constant public static final androidx.paging.PagedList.LoadState LOADING;
- enum_constant public static final androidx.paging.PagedList.LoadState RETRYABLE_ERROR;
+ public abstract static sealed class PagedList.LoadState {
+ }
+
+ public static final class PagedList.LoadState.Done extends androidx.paging.PagedList.LoadState {
+ field public static final androidx.paging.PagedList.LoadState.Done! INSTANCE;
+ }
+
+ public static final class PagedList.LoadState.Error extends androidx.paging.PagedList.LoadState {
+ ctor public PagedList.LoadState.Error(Throwable error, boolean retryable);
+ method public Throwable component1();
+ method public boolean component2();
+ method public androidx.paging.PagedList.LoadState.Error copy(Throwable error, boolean retryable);
+ method public Throwable getError();
+ method public boolean getRetryable();
+ }
+
+ public static final class PagedList.LoadState.Idle extends androidx.paging.PagedList.LoadState {
+ field public static final androidx.paging.PagedList.LoadState.Idle! INSTANCE;
+ }
+
+ public static final class PagedList.LoadState.Loading extends androidx.paging.PagedList.LoadState {
+ field public static final androidx.paging.PagedList.LoadState.Loading! INSTANCE;
}
@RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP) public abstract static class PagedList.LoadStateManager {
- method public final void dispatchCurrentLoadState(kotlin.jvm.functions.Function3<? super androidx.paging.PagedList.LoadType,? super androidx.paging.PagedList.LoadState,? super java.lang.Throwable,kotlin.Unit> callback);
- method public final androidx.paging.PagedList.LoadState getEnd();
- method public final androidx.paging.PagedList.LoadState getRefresh();
- method public final androidx.paging.PagedList.LoadState getStart();
- method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP) public abstract void onStateChanged(androidx.paging.PagedList.LoadType type, androidx.paging.PagedList.LoadState state, Throwable? error);
- method public final void setState(androidx.paging.PagedList.LoadType type, androidx.paging.PagedList.LoadState state, Throwable? error);
- property public final androidx.paging.PagedList.LoadState end;
- property public final androidx.paging.PagedList.LoadState refresh;
- property public final androidx.paging.PagedList.LoadState start;
- }
-
- public enum PagedList.LoadType {
- enum_constant public static final androidx.paging.PagedList.LoadType END;
- enum_constant public static final androidx.paging.PagedList.LoadType REFRESH;
- enum_constant public static final androidx.paging.PagedList.LoadType START;
+ method public final void dispatchCurrentLoadState(kotlin.jvm.functions.Function2<? super androidx.paging.PageLoadType,? super androidx.paging.PagedList.LoadState,kotlin.Unit> callback);
+ method public final androidx.paging.PagedList.LoadState getEndState();
+ method public final androidx.paging.PagedList.LoadState getRefreshState();
+ method public final androidx.paging.PagedList.LoadState getStartState();
+ method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP) public abstract void onStateChanged(androidx.paging.PageLoadType type, androidx.paging.PagedList.LoadState state);
+ method public final void setEndState(androidx.paging.PagedList.LoadState p);
+ method public final void setRefreshState(androidx.paging.PagedList.LoadState p);
+ method public final void setStartState(androidx.paging.PagedList.LoadState p);
+ method public final void setState(androidx.paging.PageLoadType type, androidx.paging.PagedList.LoadState state);
+ property public final androidx.paging.PagedList.LoadState endState;
+ property public final androidx.paging.PagedList.LoadState refreshState;
+ property public final androidx.paging.PagedList.LoadState startState;
}
public final class PagedListConfigKt {
@@ -292,16 +308,16 @@
}
public static final class PagedSource.LoadParams<Key> {
- ctor public PagedSource.LoadParams(androidx.paging.PagedSource.LoadType loadType, Key? key, int loadSize, boolean placeholdersEnabled, int pageSize);
- method public androidx.paging.PagedSource.LoadType component1();
+ ctor public PagedSource.LoadParams(androidx.paging.PageLoadType loadType, Key? key, int loadSize, boolean placeholdersEnabled, int pageSize);
+ method public androidx.paging.PageLoadType component1();
method public Key? component2();
method public int component3();
method public boolean component4();
method public int component5();
- method public androidx.paging.PagedSource.LoadParams<Key> copy(androidx.paging.PagedSource.LoadType loadType, Key? key, int loadSize, boolean placeholdersEnabled, int pageSize);
+ method public androidx.paging.PagedSource.LoadParams<Key> copy(androidx.paging.PageLoadType loadType, Key? key, int loadSize, boolean placeholdersEnabled, int pageSize);
method public Key? getKey();
method public int getLoadSize();
- method public androidx.paging.PagedSource.LoadType getLoadType();
+ method public androidx.paging.PageLoadType getLoadType();
method public int getPageSize();
method public boolean getPlaceholdersEnabled();
}
@@ -326,12 +342,6 @@
public static final class PagedSource.LoadResult.Companion {
}
- public enum PagedSource.LoadType {
- enum_constant public static final androidx.paging.PagedSource.LoadType END;
- enum_constant public static final androidx.paging.PagedSource.LoadType INITIAL;
- enum_constant public static final androidx.paging.PagedSource.LoadType START;
- }
-
public final class PagedSourceKt {
ctor public PagedSourceKt();
}
diff --git a/paging/common/api/restricted_current.txt b/paging/common/api/restricted_current.txt
index b3bd226..976e9a3 100644
--- a/paging/common/api/restricted_current.txt
+++ b/paging/common/api/restricted_current.txt
@@ -44,7 +44,6 @@
method @AnyThread public void onInvalidated();
}
-
public static final class DataSource.Params<K> {
method public int getInitialLoadSize();
method public K? getKey();
@@ -139,10 +138,16 @@
ctor public PageKeyedDataSourceKt();
}
+ public enum PageLoadType {
+ enum_constant public static final androidx.paging.PageLoadType END;
+ enum_constant public static final androidx.paging.PageLoadType REFRESH;
+ enum_constant public static final androidx.paging.PageLoadType START;
+ }
+
public abstract class PagedList<T> extends java.util.AbstractList<T> {
ctor @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP) public PagedList(kotlinx.coroutines.CoroutineScope coroutineScope, androidx.paging.PagedSource<?,T> pagedSource, androidx.paging.PagedStorage<T> storage, kotlinx.coroutines.CoroutineDispatcher notifyDispatcher, kotlinx.coroutines.CoroutineDispatcher backgroundDispatcher, androidx.paging.PagedList.BoundaryCallback<T>? boundaryCallback, androidx.paging.PagedList.Config config);
method public void addWeakCallback(java.util.List<? extends T>? previousSnapshot, androidx.paging.PagedList.Callback callback);
- method public void addWeakLoadStateListener(kotlin.jvm.functions.Function3<? super androidx.paging.PagedList.LoadType,? super androidx.paging.PagedList.LoadState,? super java.lang.Throwable,kotlin.Unit> listener);
+ method public void addWeakLoadStateListener(kotlin.jvm.functions.Function2<? super androidx.paging.PageLoadType,? super androidx.paging.PagedList.LoadState,kotlin.Unit> listener);
method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP) public static final suspend <K, T> Object create(androidx.paging.PagedSource<K,T> p, kotlinx.coroutines.CoroutineScope pagedSource, kotlinx.coroutines.CoroutineDispatcher coroutineScope, kotlinx.coroutines.CoroutineDispatcher notifyDispatcher, kotlinx.coroutines.CoroutineDispatcher fetchDispatcher, androidx.paging.PagedList.BoundaryCallback<T>? initialFetchDispatcher, androidx.paging.PagedList.Config boundaryCallback, K? config, kotlin.coroutines.Continuation<? super androidx.paging.PagedList<T>> key);
method public abstract void detach();
method public T? get(int index);
@@ -150,7 +155,6 @@
method @Deprecated public final androidx.paging.DataSource<?,T> getDataSource();
method public abstract Object? getLastKey();
method public int getLoadedCount();
- method public final androidx.paging.PagedSource<?,T> getPagedSource();
method public int getPositionOffset();
method public int getSize();
method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP) public final androidx.paging.PagedStorage<T> getStorage();
@@ -158,9 +162,9 @@
method public boolean isImmutable();
method public void loadAround(int index);
method public void removeWeakCallback(androidx.paging.PagedList.Callback callback);
- method public void removeWeakLoadStateListener(kotlin.jvm.functions.Function3<? super androidx.paging.PagedList.LoadType,? super androidx.paging.PagedList.LoadState,? super java.lang.Throwable,kotlin.Unit> listener);
+ method public void removeWeakLoadStateListener(kotlin.jvm.functions.Function2<? super androidx.paging.PageLoadType,? super androidx.paging.PagedList.LoadState,kotlin.Unit> listener);
method public void retry();
- method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP) public void setInitialLoadState(androidx.paging.PagedList.LoadState loadState, Throwable? error);
+ method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP) public void setInitialLoadState(androidx.paging.PageLoadType loadType, androidx.paging.PagedList.LoadState loadState);
method public java.util.List<T> snapshot();
property public androidx.paging.PagedList.Config config;
property @Deprecated public final androidx.paging.DataSource<?,T> dataSource;
@@ -168,7 +172,6 @@
property public boolean isImmutable;
property public abstract Object? lastKey;
property public int loadedCount;
- property public final androidx.paging.PagedSource<?,T> pagedSource;
property public int positionOffset;
property public int size;
}
@@ -226,30 +229,43 @@
method public androidx.paging.PagedList.Config.Builder setPrefetchDistance(@IntRange(from=0) int prefetchDistance);
}
- public enum PagedList.LoadState {
- enum_constant public static final androidx.paging.PagedList.LoadState DONE;
- enum_constant public static final androidx.paging.PagedList.LoadState ERROR;
- enum_constant public static final androidx.paging.PagedList.LoadState IDLE;
- enum_constant public static final androidx.paging.PagedList.LoadState LOADING;
- enum_constant public static final androidx.paging.PagedList.LoadState RETRYABLE_ERROR;
+ public abstract static sealed class PagedList.LoadState {
+ }
+
+ public static final class PagedList.LoadState.Done extends androidx.paging.PagedList.LoadState {
+ field public static final androidx.paging.PagedList.LoadState.Done! INSTANCE;
+ }
+
+ public static final class PagedList.LoadState.Error extends androidx.paging.PagedList.LoadState {
+ ctor public PagedList.LoadState.Error(Throwable error, boolean retryable);
+ method public Throwable component1();
+ method public boolean component2();
+ method public androidx.paging.PagedList.LoadState.Error copy(Throwable error, boolean retryable);
+ method public Throwable getError();
+ method public boolean getRetryable();
+ }
+
+ public static final class PagedList.LoadState.Idle extends androidx.paging.PagedList.LoadState {
+ field public static final androidx.paging.PagedList.LoadState.Idle! INSTANCE;
+ }
+
+ public static final class PagedList.LoadState.Loading extends androidx.paging.PagedList.LoadState {
+ field public static final androidx.paging.PagedList.LoadState.Loading! INSTANCE;
}
@RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP) public abstract static class PagedList.LoadStateManager {
- method public final void dispatchCurrentLoadState(kotlin.jvm.functions.Function3<? super androidx.paging.PagedList.LoadType,? super androidx.paging.PagedList.LoadState,? super java.lang.Throwable,kotlin.Unit> callback);
- method public final androidx.paging.PagedList.LoadState getEnd();
- method public final androidx.paging.PagedList.LoadState getRefresh();
- method public final androidx.paging.PagedList.LoadState getStart();
- method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP) public abstract void onStateChanged(androidx.paging.PagedList.LoadType type, androidx.paging.PagedList.LoadState state, Throwable? error);
- method public final void setState(androidx.paging.PagedList.LoadType type, androidx.paging.PagedList.LoadState state, Throwable? error);
- property public final androidx.paging.PagedList.LoadState end;
- property public final androidx.paging.PagedList.LoadState refresh;
- property public final androidx.paging.PagedList.LoadState start;
- }
-
- public enum PagedList.LoadType {
- enum_constant public static final androidx.paging.PagedList.LoadType END;
- enum_constant public static final androidx.paging.PagedList.LoadType REFRESH;
- enum_constant public static final androidx.paging.PagedList.LoadType START;
+ method public final void dispatchCurrentLoadState(kotlin.jvm.functions.Function2<? super androidx.paging.PageLoadType,? super androidx.paging.PagedList.LoadState,kotlin.Unit> callback);
+ method public final androidx.paging.PagedList.LoadState getEndState();
+ method public final androidx.paging.PagedList.LoadState getRefreshState();
+ method public final androidx.paging.PagedList.LoadState getStartState();
+ method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP) public abstract void onStateChanged(androidx.paging.PageLoadType type, androidx.paging.PagedList.LoadState state);
+ method public final void setEndState(androidx.paging.PagedList.LoadState p);
+ method public final void setRefreshState(androidx.paging.PagedList.LoadState p);
+ method public final void setStartState(androidx.paging.PagedList.LoadState p);
+ method public final void setState(androidx.paging.PageLoadType type, androidx.paging.PagedList.LoadState state);
+ property public final androidx.paging.PagedList.LoadState endState;
+ property public final androidx.paging.PagedList.LoadState refreshState;
+ property public final androidx.paging.PagedList.LoadState startState;
}
public final class PagedListConfigKt {
@@ -292,16 +308,16 @@
}
public static final class PagedSource.LoadParams<Key> {
- ctor public PagedSource.LoadParams(androidx.paging.PagedSource.LoadType loadType, Key? key, int loadSize, boolean placeholdersEnabled, int pageSize);
- method public androidx.paging.PagedSource.LoadType component1();
+ ctor public PagedSource.LoadParams(androidx.paging.PageLoadType loadType, Key? key, int loadSize, boolean placeholdersEnabled, int pageSize);
+ method public androidx.paging.PageLoadType component1();
method public Key? component2();
method public int component3();
method public boolean component4();
method public int component5();
- method public androidx.paging.PagedSource.LoadParams<Key> copy(androidx.paging.PagedSource.LoadType loadType, Key? key, int loadSize, boolean placeholdersEnabled, int pageSize);
+ method public androidx.paging.PagedSource.LoadParams<Key> copy(androidx.paging.PageLoadType loadType, Key? key, int loadSize, boolean placeholdersEnabled, int pageSize);
method public Key? getKey();
method public int getLoadSize();
- method public androidx.paging.PagedSource.LoadType getLoadType();
+ method public androidx.paging.PageLoadType getLoadType();
method public int getPageSize();
method public boolean getPlaceholdersEnabled();
}
@@ -326,12 +342,6 @@
public static final class PagedSource.LoadResult.Companion {
}
- public enum PagedSource.LoadType {
- enum_constant public static final androidx.paging.PagedSource.LoadType END;
- enum_constant public static final androidx.paging.PagedSource.LoadType INITIAL;
- enum_constant public static final androidx.paging.PagedSource.LoadType START;
- }
-
public final class PagedSourceKt {
ctor public PagedSourceKt();
}
diff --git a/paging/common/src/main/kotlin/androidx/paging/ContiguousPagedList.kt b/paging/common/src/main/kotlin/androidx/paging/ContiguousPagedList.kt
index 8fc3eac..9ec9758 100644
--- a/paging/common/src/main/kotlin/androidx/paging/ContiguousPagedList.kt
+++ b/paging/common/src/main/kotlin/androidx/paging/ContiguousPagedList.kt
@@ -18,6 +18,11 @@
import androidx.annotation.MainThread
import androidx.annotation.RestrictTo
+import androidx.paging.PageLoadType.END
+import androidx.paging.PageLoadType.REFRESH
+import androidx.paging.PageLoadType.START
+import androidx.paging.PagedList.LoadState.Idle
+import androidx.paging.PagedList.LoadState.Loading
import androidx.paging.PagedSource.KeyProvider
import androidx.paging.PagedSource.LoadResult.Companion.COUNT_UNDEFINED
import kotlinx.coroutines.CoroutineDispatcher
@@ -99,7 +104,10 @@
/**
* Given a page result, apply or drop it, and return whether more loading is needed.
*/
- override fun onPageResult(type: LoadType, pageResult: PagedSource.LoadResult<*, V>): Boolean {
+ override fun onPageResult(
+ type: PageLoadType,
+ pageResult: PagedSource.LoadResult<*, V>
+ ): Boolean {
var continueLoading = false
val page = pageResult.data
@@ -113,7 +121,7 @@
page.size
)
- if (type == LoadType.END) {
+ if (type == END) {
if (skipNewPage && !trimFromFront) {
// don't append this data, drop it
appendItemsRequested = 0
@@ -124,7 +132,7 @@
continueLoading = true
}
}
- } else if (type == LoadType.START) {
+ } else if (type == START) {
if (skipNewPage && trimFromFront) {
// don't append this data, drop it
prependItemsRequested = 0
@@ -144,7 +152,7 @@
// For simplicity (both of impl here, and contract w/ PagedSource) we don't
// allow fetches in same direction - this means reading the load state is safe.
if (trimFromFront) {
- if (pager.loadStateManager.start != LoadState.LOADING) {
+ if (pager.loadStateManager.startState !is Loading) {
if (storage.trimFromFront(
replacePagesWithNulls,
config.maxSize,
@@ -153,15 +161,11 @@
)
) {
// trimmed from front, ensure we can fetch in that dir
- pager.loadStateManager.setState(
- LoadType.START,
- LoadState.IDLE,
- null
- )
+ pager.loadStateManager.setState(START, Idle)
}
}
} else {
- if (pager.loadStateManager.end != LoadState.LOADING) {
+ if (pager.loadStateManager.endState !is Loading) {
if (storage.trimFromEnd(
replacePagesWithNulls,
config.maxSize,
@@ -169,7 +173,7 @@
this@ContiguousPagedList
)
) {
- pager.loadStateManager.setState(LoadType.END, LoadState.IDLE, null)
+ pager.loadStateManager.setState(END, Idle)
}
}
}
@@ -179,14 +183,14 @@
return continueLoading
}
- override fun onStateChanged(type: LoadType, state: LoadState, error: Throwable?) =
- dispatchStateChange(type, state, error)
+ override fun onStateChanged(type: PageLoadType, state: LoadState) =
+ dispatchStateChange(type, state)
- private fun triggerBoundaryCallback(type: LoadType, page: List<V>) {
+ private fun triggerBoundaryCallback(type: PageLoadType, page: List<V>) {
if (boundaryCallback != null) {
val deferEmpty = storage.size == 0
- val deferBegin = (!deferEmpty && type == LoadType.START && page.isEmpty())
- val deferEnd = (!deferEmpty && type == LoadType.END && page.isEmpty())
+ val deferBegin = (!deferEmpty && type == START && page.isEmpty())
+ val deferEnd = (!deferEmpty && type == END && page.isEmpty())
deferBoundaryCallbacks(deferEmpty, deferBegin, deferEnd)
}
}
@@ -195,9 +199,11 @@
super.retry()
pager.retry()
- if (pager.loadStateManager.refresh == LoadState.RETRYABLE_ERROR) {
- // Loading the next PagedList failed, signal the retry callback.
- refreshRetryCallback?.run()
+ pager.loadStateManager.refreshState.run {
+ // If loading the next PagedList failed, signal the retry callback.
+ if (this is LoadState.Error && retryable) {
+ refreshRetryCallback?.run()
+ }
}
}
@@ -231,14 +237,16 @@
if (initialResult.itemsBefore != COUNT_UNDEFINED) initialResult.itemsBefore else 0
this.lastLoad = itemsBefore + initialResult.data.size / 2
}
- triggerBoundaryCallback(LoadType.REFRESH, initialResult.data)
+ triggerBoundaryCallback(REFRESH, initialResult.data)
}
- override fun dispatchCurrentLoadState(callback: LoadStateListener) =
+ override fun dispatchCurrentLoadState(callback: LoadStateListener) {
pager.loadStateManager.dispatchCurrentLoadState(callback)
+ }
- override fun setInitialLoadState(loadState: LoadState, error: Throwable?) =
- pager.loadStateManager.setState(LoadType.REFRESH, loadState, error)
+ override fun setInitialLoadState(loadType: PageLoadType, loadState: LoadState) {
+ pager.loadStateManager.setState(loadType, loadState)
+ }
@MainThread
override fun dispatchUpdatesSinceSnapshot(snapshot: PagedList<V>, callback: Callback) {
diff --git a/paging/common/src/main/kotlin/androidx/paging/DataSource.kt b/paging/common/src/main/kotlin/androidx/paging/DataSource.kt
index f3740f3..3a90a7e 100644
--- a/paging/common/src/main/kotlin/androidx/paging/DataSource.kt
+++ b/paging/common/src/main/kotlin/androidx/paging/DataSource.kt
@@ -179,7 +179,7 @@
*
* @param function Function that runs on each loaded item, returning items of a potentially
* new type.
- * @param ToValue Type of items produced by the new DataSource, from the passed function.
+ * @param ToValue Type of items produced by the new [DataSource], from the passed function.
* @return A new [DataSource.Factory], which transforms items using the given function.
*
* @see mapByPage
@@ -198,7 +198,7 @@
*
* @param function Function that runs on each loaded item, returning items of a potentially
* new type.
- * @param ToValue Type of items produced by the new DataSource, from the passed function.
+ * @param ToValue Type of items produced by the new [DataSource], from the passed function.
* @return A new [DataSource.Factory], which transforms items using the given function.
*
* @see mapByPage
@@ -215,7 +215,7 @@
*
* @param function Function that runs on each loaded page, returning items of a potentially
* new type.
- * @param ToValue Type of items produced by the new DataSource, from the passed function.
+ * @param ToValue Type of items produced by the new [DataSource], from the passed function.
* @return A new [DataSource.Factory], which transforms items using the given function.
*
* @see map
@@ -238,7 +238,7 @@
*
* @param function Function that runs on each loaded page, returning items of a potentially
* new type.
- * @param ToValue Type of items produced by the new DataSource, from the passed function.
+ * @param ToValue Type of items produced by the new [DataSource], from the passed function.
* @return A new [DataSource.Factory], which transforms items using the given function.
*
* @see map
@@ -278,7 +278,7 @@
* @param function Function that runs on each loaded page, returning items of a potentially
* new type.
* @param ToValue Type of items produced by the new DataSource, from the passed function.
- * @return A new DataSource, which transforms items using the given function.
+ * @return A new [DataSource], which transforms items using the given function.
*
* @see map
* @see DataSource.Factory.map
@@ -431,21 +431,11 @@
}
/**
- * @hide
- */
- @RestrictTo(RestrictTo.Scope.LIBRARY)
- enum class LoadType {
- INITIAL,
- START,
- END
- }
-
- /**
* @param K Type of the key used to query the [DataSource].
* @property key Can be `null` for init, otherwise non-null
*/
class Params<K : Any> internal constructor(
- internal val type: LoadType,
+ internal val type: PageLoadType,
val key: K?,
val initialLoadSize: Int,
val placeholdersEnabled: Boolean,
diff --git a/paging/common/src/main/kotlin/androidx/paging/InitialPagedList.kt b/paging/common/src/main/kotlin/androidx/paging/InitialPagedList.kt
index 7407055..a3628e8 100644
--- a/paging/common/src/main/kotlin/androidx/paging/InitialPagedList.kt
+++ b/paging/common/src/main/kotlin/androidx/paging/InitialPagedList.kt
@@ -23,7 +23,7 @@
/**
* InitialPagedList is an empty placeholder that's sent at the front of a stream of PagedLists.
*
- * It's used solely for listening to [PagedList.LoadType.REFRESH] loading events, and retrying
+ * It's used solely for listening to [PageLoadType.REFRESH] loading events, and retrying
* any errors that occur during initial load.
*
* @hide
diff --git a/paging/common/src/main/kotlin/androidx/paging/ItemKeyedDataSource.kt b/paging/common/src/main/kotlin/androidx/paging/ItemKeyedDataSource.kt
index 969e289..074c08a 100644
--- a/paging/common/src/main/kotlin/androidx/paging/ItemKeyedDataSource.kt
+++ b/paging/common/src/main/kotlin/androidx/paging/ItemKeyedDataSource.kt
@@ -38,8 +38,8 @@
* [Retrofit](https://square.github.io/retrofit/), while handling swipe-to-refresh, network errors,
* and retry.
*
- * @param Key Type of data used to query Value types out of the DataSource.
- * @param Value Type of items being loaded by the DataSource.
+ * @param Key Type of data used to query Value types out of the [DataSource].
+ * @param Value Type of items being loaded by the [DataSource].
*/
abstract class ItemKeyedDataSource<Key : Any, Value : Any> : DataSource<Key, Value>(ITEM_KEYED) {
@@ -181,15 +181,15 @@
@Suppress("RedundantVisibilityModifier") // Metalava doesn't inherit visibility properly.
internal final override suspend fun load(params: Params<Key>): BaseResult<Value> {
return when (params.type) {
- LoadType.INITIAL -> loadInitial(
+ PageLoadType.REFRESH -> loadInitial(
LoadInitialParams(
params.key,
params.initialLoadSize,
params.placeholdersEnabled
)
)
- LoadType.START -> loadBefore(LoadParams(params.key!!, params.pageSize))
- LoadType.END -> loadAfter(LoadParams(params.key!!, params.pageSize))
+ PageLoadType.START -> loadBefore(LoadParams(params.key!!, params.pageSize))
+ PageLoadType.END -> loadAfter(LoadParams(params.key!!, params.pageSize))
}
}
diff --git a/paging/common/src/main/kotlin/androidx/paging/PageKeyedDataSource.kt b/paging/common/src/main/kotlin/androidx/paging/PageKeyedDataSource.kt
index 25d5f3e..a62d597 100644
--- a/paging/common/src/main/kotlin/androidx/paging/PageKeyedDataSource.kt
+++ b/paging/common/src/main/kotlin/androidx/paging/PageKeyedDataSource.kt
@@ -211,15 +211,15 @@
*/
@Suppress("RedundantVisibilityModifier") // Metalava doesn't inherit visibility properly.
internal final override suspend fun load(params: Params<Key>): BaseResult<Value> = when {
- params.type == LoadType.INITIAL -> loadInitial(
+ params.type == PageLoadType.REFRESH -> loadInitial(
LoadInitialParams(
params.initialLoadSize,
params.placeholdersEnabled
)
)
params.key == null -> BaseResult.empty()
- params.type == LoadType.START -> loadBefore(LoadParams(params.key, params.pageSize))
- params.type == LoadType.END -> loadAfter(LoadParams(params.key, params.pageSize))
+ params.type == PageLoadType.START -> loadBefore(LoadParams(params.key, params.pageSize))
+ params.type == PageLoadType.END -> loadAfter(LoadParams(params.key, params.pageSize))
else -> throw IllegalArgumentException("Unsupported type " + params.type.toString())
}
diff --git a/paging/common/src/main/kotlin/androidx/paging/PageLoadType.kt b/paging/common/src/main/kotlin/androidx/paging/PageLoadType.kt
new file mode 100644
index 0000000..bd6ed01
--- /dev/null
+++ b/paging/common/src/main/kotlin/androidx/paging/PageLoadType.kt
@@ -0,0 +1,45 @@
+/*
+ * 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.paging
+
+/**
+ * Type of load a [PagedList] can trigger a [PagedSource] to perform.
+ *
+ * You can use a [LoadStateListener] to observe [PagedList.LoadState] of any [PageLoadType]. For UI
+ * purposes (swipe refresh, loading spinner, retry button), this is typically done by registering a
+ * [LoadStateListener] with the [androidx.paging.PagedListAdapter] or
+ * [androidx.paging.AsyncPagedListDiffer].
+ *
+ * @see PagedList.LoadState
+ */
+enum class PageLoadType {
+ /**
+ * [PagedList] content being refreshed, which can also be a result of [PagedSource]
+ * invalidation, refresh that may contain content updates, or the initial load.
+ */
+ REFRESH,
+
+ /**
+ * Load at the start of the [PagedList].
+ */
+ START,
+
+ /**
+ * Load at the end of the [PagedList].
+ */
+ END
+}
\ No newline at end of file
diff --git a/paging/common/src/main/kotlin/androidx/paging/PagedList.kt b/paging/common/src/main/kotlin/androidx/paging/PagedList.kt
index c0bde0e..f880cb6 100644
--- a/paging/common/src/main/kotlin/androidx/paging/PagedList.kt
+++ b/paging/common/src/main/kotlin/androidx/paging/PagedList.kt
@@ -26,7 +26,6 @@
import androidx.paging.PagedList.Config.Builder
import androidx.paging.PagedList.Config.Companion.MAX_SIZE_UNBOUNDED
import androidx.paging.PagedList.LoadState
-import androidx.paging.PagedList.LoadType
import androidx.paging.PagedSource.KeyProvider
import androidx.paging.futures.DirectDispatcher
import kotlinx.coroutines.CoroutineDispatcher
@@ -46,7 +45,7 @@
* Callback for changes to loading state - whether the refresh, prepend, or append is idle, loading,
* or has an error.
*
- * Used to observe the [LoadState] of any [LoadType] (REFRESH/START/END). For UI purposes (swipe
+ * Used to observe the [LoadState] of any [PageLoadType] (REFRESH/START/END). For UI purposes (swipe
* refresh, loading spinner, retry button), this is typically done by registering a
* [LoadStateListener] with the [androidx.paging.PagedListAdapter] or
* [androidx.paging.AsyncPagedListDiffer].
@@ -60,13 +59,11 @@
* REFRESH events can be used to drive a [androidx.swiperefreshlayout.widget.SwipeRefreshLayout], or
* START/END events can be used to drive loading spinner items in your `RecyclerView`.
*
- * @param type [LoadType] - START, END, or REFRESH.
- * @param state [LoadState] - IDLE, LOADING, DONE, ERROR, or RETRYABLE_ERROR
- * @param error [Throwable] if in an error state, null otherwise.
- *
+ * @see [LoadState]
+ * @see [PageLoadType]
* @see [PagedList.retry]
*/
-typealias LoadStateListener = (type: LoadType, state: LoadState, error: Throwable?) -> Unit
+typealias LoadStateListener = (type: PageLoadType, state: LoadState) -> Unit
/**
* Lazy loading list that pages in immutable content from a [PagedSource].
@@ -192,7 +189,7 @@
}
val params = PagedSource.LoadParams(
- PagedSource.LoadType.INITIAL,
+ PageLoadType.REFRESH,
key,
config.initialLoadSizeHint,
config.enablePlaceholders,
@@ -217,66 +214,40 @@
}
/**
- * Type of load a PagedList can perform.
+ * LoadState of a PagedList load - associated with a [PageLoadType]
*
- * You can use a [LoadStateListener] to observe [LoadState] of any [LoadType]. For UI purposes
- * (swipe refresh, loading spinner, retry button), this is typically done by registering a
- * Listener with the [androidx.paging.PagedListAdapter] or
- * [androidx.paging.AsyncPagedListDiffer].
- *
- * @see LoadState
- */
- enum class LoadType {
- /**
- * PagedList content being reloaded, may contain content updates.
- */
- REFRESH,
-
- /**
- * Load at the start of the PagedList.
- */
- START,
-
- /**
- * Load at the end of the PagedList.
- */
- END
- }
-
- /**
- * State of a PagedList load - associated with a `LoadType`
- *
- * You can use a [LoadStateListener] to observe [LoadState] of any [LoadType]. For UI
+ * You can use a [LoadStateListener] to observe [LoadState] of any [PageLoadType]. For UI
* purposes (swipe refresh, loading spinner, retry button), this is typically done by
* registering a callback with the `PagedListAdapter` or `AsyncPagedListDiffer`.
+ *
+ * @see PageLoadType
*/
- enum class LoadState {
+ sealed class LoadState {
/**
* Indicates the PagedList is not currently loading, and no error currently observed.
*/
- IDLE,
+ object Idle : LoadState()
/**
* Loading is in progress.
*/
- LOADING,
+ object Loading : LoadState()
/**
* Loading is complete.
*/
- DONE,
+ object Done : LoadState()
/**
- * Loading hit a non-retryable error.
- */
- ERROR,
-
- /**
- * Loading hit a retryable error.
+ * Loading hit an error.
+ *
+ * @param error [Throwable] that caused the load operation to generate this error state.
+ * @param retryable `true if the load operation that generated this error state can be
+ * retried, `false` otherwise.
*
* @see retry
*/
- RETRYABLE_ERROR
+ data class Error(val error: Throwable, val retryable: Boolean) : LoadState()
}
/**
@@ -507,7 +478,7 @@
* Creates a [PagedList] asynchronously with the given parameters.
*
* This call will dispatch [PagedSource.load] immediately with
- * [PagedSource.LoadType.INITIAL], and return a [PagedList] once it completes, triggering
+ * [PageLoadType.REFRESH], and return a [PagedList] once it completes, triggering
* [loadStateListeners].
*
* @throws IllegalArgumentException if [notifyDispatcher] or [fetchDispatcher] are not set.
@@ -900,56 +871,40 @@
*/
@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
abstract class LoadStateManager {
- var refresh = LoadState.IDLE
- private set
- private var refreshError: Throwable? = null
- var start = LoadState.IDLE
- private set
- private var startError: Throwable? = null
- var end = LoadState.IDLE
- private set
- private var endError: Throwable? = null
+ var refreshState: LoadState = LoadState.Idle
+ var startState: LoadState = LoadState.Idle
+ var endState: LoadState = LoadState.Idle
- fun setState(type: LoadType, state: LoadState, error: Throwable?) {
- val expectError = state == LoadState.RETRYABLE_ERROR || state == LoadState.ERROR
- val hasError = error != null
- if (expectError != hasError) {
- throw IllegalArgumentException(
- "Error states must be accompanied by a throwable, other states must not"
- )
- }
-
+ fun setState(type: PageLoadType, state: LoadState) {
// deduplicate signals
when (type) {
- LoadType.REFRESH -> {
- if (refresh == state && refreshError == error) return
- refresh = state
- refreshError = error
+ PageLoadType.REFRESH -> {
+ if (refreshState == state) return
+ refreshState = state
}
- LoadType.START -> {
- if (start == state && startError == error) return
- start = state
- startError = error
+ PageLoadType.START -> {
+ if (startState == state) return
+ startState = state
}
- LoadType.END -> {
- if (end == state && endError == error) return
- end = state
- endError = error
+ PageLoadType.END -> {
+ if (endState == state) return
+ endState = state
}
}
- onStateChanged(type, state, error)
+
+ onStateChanged(type, state)
}
/**
* @hide
*/
@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) // protected otherwise.
- abstract fun onStateChanged(type: LoadType, state: LoadState, error: Throwable?)
+ abstract fun onStateChanged(type: PageLoadType, state: LoadState)
fun dispatchCurrentLoadState(callback: LoadStateListener) {
- callback(LoadType.REFRESH, refresh, refreshError)
- callback(LoadType.START, start, startError)
- callback(LoadType.END, end, endError)
+ callback(PageLoadType.REFRESH, refreshState)
+ callback(PageLoadType.START, startState)
+ callback(PageLoadType.END, endState)
}
}
@@ -1042,16 +997,20 @@
/**
* The [PagedSource] that provides data to this [PagedList].
+ *
+ * @hide
*/
+ @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
val pagedSource: PagedSource<*, T>
/**
* @throws IllegalStateException if this [PagedList] was instantiated without a
- * [PagedSourceWrapper] wrapping a backing [DataSource]
+ * wrapping a backing [DataSource]
*/
@Deprecated(
- message = "DataSource is deprecated and has been replaced by PagedSource",
- replaceWith = ReplaceWith("pagedSource")
+ message = "DataSource is deprecated and has been replaced by PagedSource. PagedList " +
+ "offers indirect ways of controlling fetch ('loadAround()', 'retry()') so that " +
+ "you should not need to access the DataSource/PagedSource."
)
val dataSource: DataSource<*, T>
get() {
@@ -1160,7 +1119,7 @@
* @hide
*/
@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
- open fun setInitialLoadState(loadState: LoadState, error: Throwable?) {
+ open fun setInitialLoadState(loadType: PageLoadType, loadState: LoadState) {
}
/**
@@ -1187,9 +1146,9 @@
this.refreshRetryCallback = refreshRetryCallback
}
- internal fun dispatchStateChange(type: LoadType, state: LoadState, error: Throwable?) {
+ internal fun dispatchStateChange(type: PageLoadType, state: LoadState) {
loadStateListeners.removeAll { it.get() == null }
- loadStateListeners.forEach { it.get()?.invoke(type, state, error) }
+ loadStateListeners.forEach { it.get()?.invoke(type, state) }
}
/**
diff --git a/paging/common/src/main/kotlin/androidx/paging/PagedSource.kt b/paging/common/src/main/kotlin/androidx/paging/PagedSource.kt
index 38feb36..6b6c852 100644
--- a/paging/common/src/main/kotlin/androidx/paging/PagedSource.kt
+++ b/paging/common/src/main/kotlin/androidx/paging/PagedSource.kt
@@ -98,9 +98,6 @@
* passed to a [PagedList] to be displayed in a `RecyclerView`
*/
abstract class PagedSource<Key : Any, Value : Any> {
- enum class LoadType {
- INITIAL, START, END
- }
/**
* Params for generic load request on a [PagedSource].
@@ -109,9 +106,9 @@
*/
data class LoadParams<Key : Any>(
/**
- * Type, for different behavior, e.g. only count initial load
+ * [PageLoadType], for different behavior, e.g. only count initial load
*/
- val loadType: LoadType,
+ val loadType: PageLoadType,
/**
* Key for the page to be loaded
*/
diff --git a/paging/common/src/main/kotlin/androidx/paging/PagedSourceWrapper.kt b/paging/common/src/main/kotlin/androidx/paging/PagedSourceWrapper.kt
index c6bb7a7..31955c7 100644
--- a/paging/common/src/main/kotlin/androidx/paging/PagedSourceWrapper.kt
+++ b/paging/common/src/main/kotlin/androidx/paging/PagedSourceWrapper.kt
@@ -43,14 +43,8 @@
}
override suspend fun load(params: LoadParams<Key>): LoadResult<Key, Value> {
- val loadType = when (params.loadType) {
- LoadType.INITIAL -> DataSource.LoadType.INITIAL
- LoadType.START -> DataSource.LoadType.START
- LoadType.END -> DataSource.LoadType.END
- }
-
val dataSourceParams = DataSource.Params(
- loadType,
+ params.loadType,
params.key,
params.loadSize,
params.placeholdersEnabled,
diff --git a/paging/common/src/main/kotlin/androidx/paging/PagedStorage.kt b/paging/common/src/main/kotlin/androidx/paging/PagedStorage.kt
index 96708fc..e198b30 100644
--- a/paging/common/src/main/kotlin/androidx/paging/PagedStorage.kt
+++ b/paging/common/src/main/kotlin/androidx/paging/PagedStorage.kt
@@ -301,7 +301,7 @@
)
}
- override fun onPageResultResolution(type: PagedList.LoadType, result: LoadResult<*, T>) {
+ override fun onPageResultResolution(type: PageLoadType, result: LoadResult<*, T>) {
// ignored
}
diff --git a/paging/common/src/main/kotlin/androidx/paging/Pager.kt b/paging/common/src/main/kotlin/androidx/paging/Pager.kt
index cf42fd4..848eb8f 100644
--- a/paging/common/src/main/kotlin/androidx/paging/Pager.kt
+++ b/paging/common/src/main/kotlin/androidx/paging/Pager.kt
@@ -17,7 +17,6 @@
package androidx.paging
import androidx.paging.PagedList.LoadState
-import androidx.paging.PagedList.LoadType
import androidx.paging.PagedSource.KeyProvider
import androidx.paging.PagedSource.LoadParams
import androidx.paging.PagedSource.LoadResult
@@ -43,8 +42,9 @@
private val detached = AtomicBoolean(false)
var loadStateManager = object : PagedList.LoadStateManager() {
- override fun onStateChanged(type: LoadType, state: LoadState, error: Throwable?) =
- pageConsumer.onStateChanged(type, state, error)
+ override fun onStateChanged(type: PageLoadType, state: LoadState) {
+ pageConsumer.onStateChanged(type, state)
+ }
}
val isDetached
@@ -53,7 +53,7 @@
init {
prevKey = result.prevKey
nextKey = result.nextKey
- this.adjacentProvider.onPageResultResolution(LoadType.REFRESH, result)
+ this.adjacentProvider.onPageResultResolution(PageLoadType.REFRESH, result)
totalCount = when (result.counted) {
// only one of leadingNulls / offset may be used
true -> result.itemsBefore + result.data.size + result.itemsAfter
@@ -61,7 +61,7 @@
}
}
- private fun scheduleLoad(type: LoadType, params: LoadParams<K>) {
+ private fun scheduleLoad(type: PageLoadType, params: LoadParams<K>) {
// Listen on the BG thread if the paged source is invalid, since it can be expensive.
pagedListScope.launch(fetchDispatcher) {
try {
@@ -85,46 +85,46 @@
}
}
- private fun onLoadSuccess(type: LoadType, value: LoadResult<K, V>) {
+ private fun onLoadSuccess(type: PageLoadType, value: LoadResult<K, V>) {
if (isDetached) return // abort!
adjacentProvider.onPageResultResolution(type, value)
if (pageConsumer.onPageResult(type, value)) {
when (type) {
- LoadType.START -> {
+ PageLoadType.START -> {
prevKey = value.prevKey
schedulePrepend()
}
- LoadType.END -> {
+ PageLoadType.END -> {
nextKey = value.nextKey
scheduleAppend()
}
else -> throw IllegalStateException("Can only fetch more during append/prepend")
}
} else {
- val state = if (value.data.isEmpty()) LoadState.DONE else LoadState.IDLE
- loadStateManager.setState(type, state, null)
+ if (value.data.isEmpty()) {
+ loadStateManager.setState(type, LoadState.Done)
+ } else {
+ loadStateManager.setState(type, LoadState.Idle)
+ }
}
}
- private fun onLoadError(type: LoadType, throwable: Throwable) {
+ private fun onLoadError(type: PageLoadType, throwable: Throwable) {
if (isDetached) return // abort!
// TODO: handle nesting
- val state = when {
- source.isRetryableError(throwable) -> LoadState.RETRYABLE_ERROR
- else -> LoadState.ERROR
- }
- loadStateManager.setState(type, state, throwable)
+ val state = LoadState.Error(throwable, source.isRetryableError(throwable))
+ loadStateManager.setState(type, state)
}
fun trySchedulePrepend() {
- if (loadStateManager.start == LoadState.IDLE) schedulePrepend()
+ if (loadStateManager.startState is LoadState.Idle) schedulePrepend()
}
fun tryScheduleAppend() {
- if (loadStateManager.end == LoadState.IDLE) scheduleAppend()
+ if (loadStateManager.endState is LoadState.Idle) scheduleAppend()
}
private fun canPrepend() = when (totalCount) {
@@ -143,7 +143,7 @@
private fun schedulePrepend() {
if (!canPrepend()) {
- onLoadSuccess(LoadType.START, LoadResult.empty())
+ onLoadSuccess(PageLoadType.START, LoadResult.empty())
return
}
@@ -156,21 +156,21 @@
is KeyProvider.ItemKey -> keyProvider.getKey(adjacentProvider.firstLoadedItem!!)
}
- loadStateManager.setState(LoadType.START, LoadState.LOADING, null)
+ loadStateManager.setState(PageLoadType.START, LoadState.Loading)
val loadParams = LoadParams(
- PagedSource.LoadType.START,
+ PageLoadType.START,
key,
config.pageSize,
config.enablePlaceholders,
config.pageSize
)
- scheduleLoad(LoadType.START, loadParams)
+ scheduleLoad(PageLoadType.START, loadParams)
}
private fun scheduleAppend() {
if (!canAppend()) {
- onLoadSuccess(LoadType.END, LoadResult.empty())
+ onLoadSuccess(PageLoadType.END, LoadResult.empty())
return
}
@@ -185,20 +185,24 @@
)
}
- loadStateManager.setState(LoadType.END, LoadState.LOADING, null)
+ loadStateManager.setState(PageLoadType.END, LoadState.Loading)
val loadParams = LoadParams(
- PagedSource.LoadType.END,
+ PageLoadType.END,
key,
config.pageSize,
config.enablePlaceholders,
config.pageSize
)
- scheduleLoad(LoadType.END, loadParams)
+ scheduleLoad(PageLoadType.END, loadParams)
}
fun retry() {
- if (loadStateManager.start == LoadState.RETRYABLE_ERROR) schedulePrepend()
- if (loadStateManager.end == LoadState.RETRYABLE_ERROR) scheduleAppend()
+ loadStateManager.startState.run {
+ if (this is LoadState.Error && retryable) schedulePrepend()
+ }
+ loadStateManager.endState.run {
+ if (this is LoadState.Error && retryable) scheduleAppend()
+ }
}
fun detach() = detached.set(true)
@@ -207,9 +211,9 @@
/**
* @return `true` if we need to fetch more
*/
- fun onPageResult(type: LoadType, pageResult: LoadResult<*, V>): Boolean
+ fun onPageResult(type: PageLoadType, pageResult: LoadResult<*, V>): Boolean
- fun onStateChanged(type: LoadType, state: LoadState, error: Throwable?)
+ fun onStateChanged(type: PageLoadType, state: LoadState)
}
internal interface AdjacentProvider<V : Any> {
@@ -225,7 +229,7 @@
* implementation of the AdjacentProvider to handle this (generally by ignoring this call if
* dropping is supported).
*/
- fun onPageResultResolution(type: LoadType, result: LoadResult<*, V>)
+ fun onPageResultResolution(type: PageLoadType, result: LoadResult<*, V>)
}
internal class SimpleAdjacentProvider<V : Any> : AdjacentProvider<V> {
@@ -242,16 +246,16 @@
private var leadingUnloadedCount: Int = 0
private var trailingUnloadedCount: Int = 0
- override fun onPageResultResolution(type: LoadType, result: LoadResult<*, V>) {
+ override fun onPageResultResolution(type: PageLoadType, result: LoadResult<*, V>) {
if (result.data.isEmpty()) return
- if (type == LoadType.START) {
+ if (type == PageLoadType.START) {
firstLoadedItemIndex -= result.data.size
firstLoadedItem = result.data[0]
if (counted) {
leadingUnloadedCount -= result.data.size
}
- } else if (type == LoadType.END) {
+ } else if (type == PageLoadType.END) {
lastLoadedItemIndex += result.data.size
lastLoadedItem = result.data.last()
if (counted) {
diff --git a/paging/common/src/main/kotlin/androidx/paging/PositionalDataSource.kt b/paging/common/src/main/kotlin/androidx/paging/PositionalDataSource.kt
index 64b9fb4..8563b66 100644
--- a/paging/common/src/main/kotlin/androidx/paging/PositionalDataSource.kt
+++ b/paging/common/src/main/kotlin/androidx/paging/PositionalDataSource.kt
@@ -323,7 +323,7 @@
@Suppress("RedundantVisibilityModifier") // Metalava doesn't inherit visibility properly.
internal final override suspend fun load(params: Params<Int>): BaseResult<T> {
- if (params.type == LoadType.INITIAL) {
+ if (params.type == PageLoadType.REFRESH) {
var initialPosition = 0
var initialLoadSize = params.initialLoadSize
if (params.key != null) {
@@ -352,7 +352,7 @@
} else {
var startIndex = params.key!!
var loadSize = params.pageSize
- if (params.type == LoadType.START) {
+ if (params.type == PageLoadType.START) {
loadSize = minOf(loadSize, startIndex + 1)
startIndex = startIndex - loadSize + 1
}
diff --git a/paging/common/src/test/kotlin/androidx/paging/ContiguousPagedListTest.kt b/paging/common/src/test/kotlin/androidx/paging/ContiguousPagedListTest.kt
index 03934fe..5d1529f 100644
--- a/paging/common/src/test/kotlin/androidx/paging/ContiguousPagedListTest.kt
+++ b/paging/common/src/test/kotlin/androidx/paging/ContiguousPagedListTest.kt
@@ -17,9 +17,10 @@
package androidx.paging
import androidx.paging.ItemKeyedDataSourceTest.ItemDataSource
-import androidx.paging.PagedList.LoadState.IDLE
-import androidx.paging.PagedList.LoadState.LOADING
-import androidx.paging.PagedList.LoadState.RETRYABLE_ERROR
+import androidx.paging.PagedList.BoundaryCallback
+import androidx.paging.PagedList.Callback
+import androidx.paging.PagedList.Config
+import androidx.paging.PagedList.LoadState
import androidx.paging.futures.DirectDispatcher
import androidx.testutils.TestDispatcher
import com.nhaarman.mockitokotlin2.mock
@@ -62,9 +63,9 @@
override suspend fun load(params: LoadParams<Int>): LoadResult<Int, Item> {
return when (params.loadType) {
- LoadType.INITIAL -> loadInitial(params)
- LoadType.START -> loadBefore(params)
- LoadType.END -> loadAfter(params)
+ PageLoadType.REFRESH -> loadInitial(params)
+ PageLoadType.START -> loadBefore(params)
+ PageLoadType.END -> loadAfter(params)
}
}
@@ -82,7 +83,7 @@
val result = getClampedRange(start, start + params.loadSize)
return when {
- result == null -> throw Exception()
+ result == null -> throw EXCEPTION
placeholdersEnabled -> LoadResult(
data = result,
itemsBefore = start,
@@ -94,13 +95,13 @@
private fun loadAfter(params: LoadParams<Int>): LoadResult<Int, Item> {
val result = getClampedRange(params.key!! + 1, params.key!! + 1 + params.loadSize)
- ?: throw Exception()
+ ?: throw EXCEPTION
return LoadResult(result)
}
private fun loadBefore(params: LoadParams<Int>): LoadResult<Int, Item> {
- val result =
- getClampedRange(params.key!! - params.loadSize, params.key!!) ?: throw Exception()
+ val result = getClampedRange(params.key!! - params.loadSize, params.key!!)
+ ?: throw EXCEPTION
return LoadResult(result)
}
@@ -125,12 +126,12 @@
return data
}
- private fun <E : Any> PagedList<E>.addLoadStateCapture(desiredType: PagedList.LoadType):
- MutableList<PagedList.LoadState> {
- val list = mutableListOf<PagedList.LoadState>()
- this.addWeakLoadStateListener { type, state, _ ->
+ private fun <E : Any> PagedList<E>.addLoadStateCapture(desiredType: PageLoadType):
+ MutableList<StateChange> {
+ val list = mutableListOf<StateChange>()
+ this.addWeakLoadStateListener { type, state ->
if (type == desiredType) {
- list.add(state)
+ list.add(StateChange(type, state))
}
}
return list
@@ -168,8 +169,8 @@
initLoadSize: Int = 40,
prefetchDistance: Int = 20,
listData: List<Item> = ITEMS,
- boundaryCallback: PagedList.BoundaryCallback<Item>? = null,
- maxSize: Int = PagedList.Config.MAX_SIZE_UNBOUNDED,
+ boundaryCallback: BoundaryCallback<Item>? = null,
+ maxSize: Int = Config.MAX_SIZE_UNBOUNDED,
pagedSource: PagedSource<Int, Item> = TestPagedSource(listData)
): ContiguousPagedList<Int, Item> {
val ret = runBlocking {
@@ -180,7 +181,7 @@
backgroundThread,
DirectDispatcher,
boundaryCallback,
- PagedList.Config.Builder()
+ Config.Builder()
.setPageSize(pageSize)
.setInitialLoadSizeHint(initLoadSize)
.setPrefetchDistance(prefetchDistance)
@@ -215,7 +216,7 @@
DirectDispatcher,
DirectDispatcher,
null,
- PagedList.Config.Builder().setPageSize(10).build(),
+ Config.Builder().setPageSize(10).build(),
null
)
}
@@ -257,7 +258,7 @@
}
private fun verifyCallback(
- callback: PagedList.Callback,
+ callback: Callback,
countedPosition: Int,
uncountedPosition: Int
) {
@@ -268,12 +269,12 @@
}
}
- private fun verifyCallback(callback: PagedList.Callback, position: Int) {
+ private fun verifyCallback(callback: Callback, position: Int) {
verifyCallback(callback, position, position)
}
private fun verifyDropCallback(
- callback: PagedList.Callback,
+ callback: Callback,
countedPosition: Int,
uncountedPosition: Int
) {
@@ -284,14 +285,14 @@
}
}
- private fun verifyDropCallback(callback: PagedList.Callback, position: Int) {
+ private fun verifyDropCallback(callback: Callback, position: Int) {
verifyDropCallback(callback, position, position)
}
@Test
fun append() {
val pagedList = createCountedPagedList(0)
- val callback = mock<PagedList.Callback>()
+ val callback = mock<Callback>()
pagedList.addWeakCallback(null, callback)
verifyRange(0, 40, pagedList)
verifyZeroInteractions(callback)
@@ -307,7 +308,7 @@
@Test
fun prepend() {
val pagedList = createCountedPagedList(80)
- val callback = mock<PagedList.Callback>()
+ val callback = mock<Callback>()
pagedList.addWeakCallback(null, callback)
verifyRange(60, 40, pagedList)
verifyZeroInteractions(callback)
@@ -323,7 +324,7 @@
@Test
fun outwards() {
val pagedList = createCountedPagedList(40)
- val callback = mock<PagedList.Callback>()
+ val callback = mock<Callback>()
pagedList.addWeakCallback(null, callback)
verifyRange(20, 40, pagedList)
verifyZeroInteractions(callback)
@@ -410,7 +411,7 @@
prefetchDistance = 1,
maxSize = 70
)
- val callback = mock<PagedList.Callback>()
+ val callback = mock<Callback>()
pagedList.addWeakCallback(null, callback)
verifyRange(0, 20, pagedList)
verifyZeroInteractions(callback)
@@ -447,7 +448,7 @@
prefetchDistance = 1,
maxSize = 70
)
- val callback = mock<PagedList.Callback>()
+ val callback = mock<Callback>()
pagedList.addWeakCallback(null, callback)
verifyRange(80, 20, pagedList)
verifyZeroInteractions(callback)
@@ -493,7 +494,7 @@
drain()
verifyRange(1, 3, pagedList)
- val callback = mock<PagedList.Callback>()
+ val callback = mock<Callback>()
pagedList.addWeakCallback(null, callback)
// start a load at the beginning...
@@ -535,7 +536,7 @@
pagedList.loadAround(if (placeholdersEnabled) 2 else 0)
drain()
- val callback = mock<PagedList.Callback>()
+ val callback = mock<Callback>()
pagedList.addWeakCallback(null, callback)
// start a load at the end...
@@ -565,21 +566,30 @@
@Test
fun loadingListenerAppend() {
val pagedList = createCountedPagedList(0)
- val states = pagedList.addLoadStateCapture(PagedList.LoadType.END)
+ val states = pagedList.addLoadStateCapture(PageLoadType.END)
// No loading going on currently
- assertEquals(listOf(IDLE), states.getAllAndClear())
+ assertEquals(
+ listOf(StateChange(PageLoadType.END, LoadState.Idle)),
+ states.getAllAndClear()
+ )
verifyRange(0, 40, pagedList)
// trigger load
pagedList.loadAround(35)
mainThread.executeAll()
- assertEquals(listOf(LOADING), states.getAllAndClear())
+ assertEquals(
+ listOf(StateChange(PageLoadType.END, LoadState.Loading)),
+ states.getAllAndClear()
+ )
verifyRange(0, 40, pagedList)
// load finishes
drain()
- assertEquals(listOf(IDLE), states.getAllAndClear())
+ assertEquals(
+ listOf(StateChange(PageLoadType.END, LoadState.Idle)),
+ states.getAllAndClear()
+ )
verifyRange(0, 60, pagedList)
pagedList.pagedSource.enqueueErrorForIndex(65)
@@ -587,22 +597,34 @@
// trigger load which will error
pagedList.loadAround(55)
mainThread.executeAll()
- assertEquals(listOf(LOADING), states.getAllAndClear())
+ assertEquals(
+ listOf(StateChange(PageLoadType.END, LoadState.Loading)),
+ states.getAllAndClear()
+ )
verifyRange(0, 60, pagedList)
// load now in error state
drain()
- assertEquals(listOf(RETRYABLE_ERROR), states.getAllAndClear())
+ assertEquals(
+ listOf(StateChange(PageLoadType.END, LoadState.Error(EXCEPTION, true))),
+ states.getAllAndClear()
+ )
verifyRange(0, 60, pagedList)
// retry
pagedList.retry()
mainThread.executeAll()
- assertEquals(listOf(LOADING), states.getAllAndClear())
+ assertEquals(
+ listOf(StateChange(PageLoadType.END, LoadState.Loading)),
+ states.getAllAndClear()
+ )
// load finishes
drain()
- assertEquals(listOf(IDLE), states.getAllAndClear())
+ assertEquals(
+ listOf(StateChange(PageLoadType.END, LoadState.Idle)),
+ states.getAllAndClear()
+ )
verifyRange(0, 80, pagedList)
}
@@ -616,25 +638,41 @@
prefetchDistance = 1,
maxSize = 3
)
- val states = pagedList.addLoadStateCapture(PagedList.LoadType.START)
+ val states = pagedList.addLoadStateCapture(PageLoadType.START)
// load 3 pages - 2nd, 3rd, 4th
pagedList.loadAround(if (placeholdersEnabled) 2 else 0)
drain()
verifyRange(1, 3, pagedList)
- assertEquals(listOf(IDLE, LOADING, IDLE), states.getAllAndClear())
+ assertEquals(
+ listOf(
+ StateChange(PageLoadType.START, LoadState.Idle),
+ StateChange(PageLoadType.START, LoadState.Loading),
+ StateChange(PageLoadType.START, LoadState.Idle)
+ ),
+ states.getAllAndClear()
+ )
// start a load at the beginning, which will fail
pagedList.pagedSource.enqueueErrorForIndex(0)
pagedList.loadAround(if (placeholdersEnabled) 1 else 0)
drain()
verifyRange(1, 3, pagedList)
- assertEquals(listOf(LOADING, RETRYABLE_ERROR), states.getAllAndClear())
+ assertEquals(
+ listOf(
+ StateChange(PageLoadType.START, LoadState.Loading),
+ StateChange(PageLoadType.START, LoadState.Error(EXCEPTION, true))
+ ),
+ states.getAllAndClear()
+ )
// but without that failure being retried, access near end of list, which drops the error
pagedList.loadAround(if (placeholdersEnabled) 3 else 2)
drain()
- assertEquals(listOf(IDLE), states.getAllAndClear())
+ assertEquals(
+ listOf(StateChange(PageLoadType.START, LoadState.Idle)),
+ states.getAllAndClear()
+ )
verifyRange(2, 3, pagedList)
}
@@ -648,25 +686,41 @@
prefetchDistance = 1,
maxSize = 3
)
- val states = pagedList.addLoadStateCapture(PagedList.LoadType.END)
+ val states = pagedList.addLoadStateCapture(PageLoadType.END)
// load 3 pages - 2nd, 3rd, 4th
pagedList.loadAround(if (placeholdersEnabled) 2 else 0)
drain()
verifyRange(1, 3, pagedList)
- assertEquals(listOf(IDLE, LOADING, IDLE), states.getAllAndClear())
+ assertEquals(
+ listOf(
+ StateChange(PageLoadType.END, LoadState.Idle),
+ StateChange(PageLoadType.END, LoadState.Loading),
+ StateChange(PageLoadType.END, LoadState.Idle)
+ ),
+ states.getAllAndClear()
+ )
// start a load at the end, which will fail
pagedList.pagedSource.enqueueErrorForIndex(4)
pagedList.loadAround(if (placeholdersEnabled) 3 else 2)
drain()
verifyRange(1, 3, pagedList)
- assertEquals(listOf(LOADING, RETRYABLE_ERROR), states.getAllAndClear())
+ assertEquals(
+ listOf(
+ StateChange(PageLoadType.END, LoadState.Loading),
+ StateChange(PageLoadType.END, LoadState.Error(EXCEPTION, true))
+ ),
+ states.getAllAndClear()
+ )
// but without that failure being retried, access near start of list, which drops the error
pagedList.loadAround(if (placeholdersEnabled) 1 else 0)
drain()
- assertEquals(listOf(IDLE), states.getAllAndClear())
+ assertEquals(
+ listOf(StateChange(PageLoadType.END, LoadState.Idle)),
+ states.getAllAndClear()
+ )
verifyRange(0, 3, pagedList)
}
@@ -674,17 +728,24 @@
fun errorIntoDrop() {
// have an error, move loading range, error goes away
val pagedList = createCountedPagedList(0)
- val states = mutableListOf<PagedList.LoadState>()
- pagedList.addWeakLoadStateListener { type, state, _ ->
- if (type == PagedList.LoadType.END) {
- states.add(state)
+ val states = mutableListOf<StateChange>()
+ pagedList.addWeakLoadStateListener { type, state ->
+ if (type == PageLoadType.END) {
+ states.add(StateChange(type, state))
}
}
pagedList.pagedSource.enqueueErrorForIndex(45)
pagedList.loadAround(35)
drain()
- assertEquals(listOf(IDLE, LOADING, RETRYABLE_ERROR), states.getAllAndClear())
+ assertEquals(
+ listOf(
+ StateChange(PageLoadType.END, LoadState.Idle),
+ StateChange(PageLoadType.END, LoadState.Loading),
+ StateChange(PageLoadType.END, LoadState.Error(EXCEPTION, true))
+ ),
+ states.getAllAndClear()
+ )
verifyRange(0, 40, pagedList)
}
@@ -697,7 +758,7 @@
prefetchDistance = 30
)
- val callback = mock<PagedList.Callback>()
+ val callback = mock<Callback>()
pagedList.addWeakCallback(null, callback)
verifyRange(0, 10, pagedList)
verifyZeroInteractions(callback)
@@ -734,7 +795,7 @@
verifyRange(0, 60, snapshot)
// and verify the snapshot hasn't received them
- val callback = mock<PagedList.Callback>()
+ val callback = mock<Callback>()
pagedList.addWeakCallback(snapshot, callback)
verifyCallback(callback, 60)
verifyNoMoreInteractions(callback)
@@ -758,7 +819,7 @@
verifyRange(20, 80, pagedList)
verifyRange(40, 60, snapshot)
- val callback = mock<PagedList.Callback>()
+ val callback = mock<Callback>()
pagedList.addWeakCallback(snapshot, callback)
verifyCallback(callback, 40, 0)
verifyNoMoreInteractions(callback)
@@ -791,7 +852,7 @@
@Test
fun addWeakCallbackEmpty() {
val pagedList = createCountedPagedList(0)
- val callback = mock<PagedList.Callback>()
+ val callback = mock<Callback>()
verifyRange(0, 40, pagedList)
// capture empty snapshot
@@ -816,7 +877,7 @@
@Test
fun boundaryCallback_empty() {
@Suppress("UNCHECKED_CAST")
- val boundaryCallback = mock<PagedList.BoundaryCallback<Item>>()
+ val boundaryCallback = mock<BoundaryCallback<Item>>()
val pagedList = createCountedPagedList(
0,
listData = ArrayList(), boundaryCallback = boundaryCallback
@@ -836,7 +897,7 @@
fun boundaryCallback_singleInitialLoad() {
val shortList = ITEMS.subList(0, 4)
@Suppress("UNCHECKED_CAST")
- val boundaryCallback = mock<PagedList.BoundaryCallback<Item>>()
+ val boundaryCallback = mock<BoundaryCallback<Item>>()
val pagedList = createCountedPagedList(
0, listData = shortList,
initLoadSize = shortList.size, boundaryCallback = boundaryCallback
@@ -858,7 +919,7 @@
@Test
fun boundaryCallback_delayed() {
@Suppress("UNCHECKED_CAST")
- val boundaryCallback = mock<PagedList.BoundaryCallback<Item>>()
+ val boundaryCallback = mock<BoundaryCallback<Item>>()
val pagedList = createCountedPagedList(
90,
initLoadSize = 20, prefetchDistance = 5, boundaryCallback = boundaryCallback
@@ -914,6 +975,8 @@
return arrayOf(arrayOf(true), arrayOf(false))
}
+ val EXCEPTION = Exception()
+
private val ITEMS = List(100) { Item(it) }
}
}
diff --git a/paging/common/src/test/kotlin/androidx/paging/PagedListTest.kt b/paging/common/src/test/kotlin/androidx/paging/PagedListTest.kt
index eba29cb..c05bcf2 100644
--- a/paging/common/src/test/kotlin/androidx/paging/PagedListTest.kt
+++ b/paging/common/src/test/kotlin/androidx/paging/PagedListTest.kt
@@ -16,6 +16,12 @@
package androidx.paging
+import androidx.paging.ContiguousPagedListTest.Companion.EXCEPTION
+import androidx.paging.PageLoadType.REFRESH
+import androidx.paging.PagedList.Builder
+import androidx.paging.PagedList.Config
+import androidx.paging.PagedList.LoadState
+import androidx.paging.PagedList.LoadStateManager
import androidx.paging.futures.DirectDispatcher
import androidx.testutils.TestDispatcher
import androidx.testutils.TestExecutor
@@ -40,7 +46,7 @@
override suspend fun load(params: LoadParams<Int>): LoadResult<Int, String> =
when (params.loadType) {
- LoadType.INITIAL -> LoadResult(
+ REFRESH -> LoadResult(
data = listOf("a"),
itemsBefore = 0,
itemsAfter = 0
@@ -59,7 +65,7 @@
@Test
fun createLegacy() {
@Suppress("DEPRECATION")
- val pagedList = PagedList.Builder(ListDataSource(ITEMS), 100)
+ val pagedList = Builder(ListDataSource(ITEMS), 100)
.setNotifyExecutor(TestExecutor())
.setFetchExecutor(TestExecutor())
.build()
@@ -69,7 +75,7 @@
@Test
fun createAsync() {
- val config = PagedList.Config.Builder()
+ val config = Config.Builder()
.setPageSize(10)
.setEnablePlaceholders(false)
.build()
@@ -109,7 +115,7 @@
override fun isRetryableError(error: Throwable) = false
}
- val config = PagedList.Config.Builder()
+ val config = Config.Builder()
.setPageSize(10)
.setEnablePlaceholders(false)
.build()
@@ -138,7 +144,7 @@
@Test
fun defaults() = runBlocking {
- val pagedList = PagedList.Builder(pagedSource, config)
+ val pagedList = Builder(pagedSource, config)
.setNotifyDispatcher(DirectDispatcher)
.setFetchDispatcher(DirectDispatcher)
.buildAsync()
@@ -146,4 +152,19 @@
assertEquals(pagedSource, pagedList.pagedSource)
assertEquals(config, pagedList.config)
}
+
+ @Test
+ fun setState_Error() {
+ var onStateChangeCalls = 0
+ val loadStateManager = object : LoadStateManager() {
+ override fun onStateChanged(type: PageLoadType, state: LoadState) {
+ onStateChangeCalls++
+ }
+ }
+
+ loadStateManager.setState(REFRESH, LoadState.Error(EXCEPTION, true))
+ loadStateManager.setState(REFRESH, LoadState.Error(EXCEPTION, true))
+
+ assertEquals(1, onStateChangeCalls)
+ }
}
diff --git a/paging/common/src/test/kotlin/androidx/paging/PagedSourceTest.kt b/paging/common/src/test/kotlin/androidx/paging/PagedSourceTest.kt
index 438fdae..e4ac159 100644
--- a/paging/common/src/test/kotlin/androidx/paging/PagedSourceTest.kt
+++ b/paging/common/src/test/kotlin/androidx/paging/PagedSourceTest.kt
@@ -19,7 +19,6 @@
import androidx.paging.PagedSource.LoadParams
import androidx.paging.PagedSource.LoadResult
import androidx.paging.PagedSource.LoadResult.Companion.COUNT_UNDEFINED
-import androidx.paging.PagedSource.LoadType
import kotlinx.coroutines.runBlocking
import org.junit.Assert.assertEquals
import org.junit.Assert.assertSame
@@ -43,7 +42,7 @@
): LoadResult<Key, Item> {
return pagedSource.load(
LoadParams(
- LoadType.INITIAL,
+ PageLoadType.REFRESH,
key,
initialLoadSize,
enablePlaceholders,
@@ -65,7 +64,7 @@
// Verify error is propagated correctly.
pagedSource.enqueueError()
- val errorParams = LoadParams(LoadType.INITIAL, key, 10, false, 10)
+ val errorParams = LoadParams(PageLoadType.REFRESH, key, 10, false, 10)
assertFailsWith<CustomException> {
pagedSource.load(errorParams)
}
@@ -197,7 +196,7 @@
runBlocking {
val key = dataSource.keyProvider.getKey(ITEMS_BY_NAME_ID[5])
- val params = LoadParams(LoadType.START, key, 5, false, 5)
+ val params = LoadParams(PageLoadType.START, key, 5, false, 5)
val observed = dataSource.load(params).data
assertEquals(ITEMS_BY_NAME_ID.subList(0, 5), observed)
@@ -205,7 +204,7 @@
// Verify error is propagated correctly.
dataSource.enqueueError()
assertFailsWith<CustomException> {
- val errorParams = LoadParams(LoadType.START, key, 5, false, 5)
+ val errorParams = LoadParams(PageLoadType.START, key, 5, false, 5)
dataSource.load(errorParams)
}
}
@@ -217,7 +216,7 @@
runBlocking {
val key = dataSource.keyProvider.getKey(ITEMS_BY_NAME_ID[5])
- val params = LoadParams(LoadType.END, key, 5, false, 5)
+ val params = LoadParams(PageLoadType.END, key, 5, false, 5)
val observed = dataSource.load(params).data
assertEquals(ITEMS_BY_NAME_ID.subList(6, 11), observed)
@@ -225,7 +224,7 @@
// Verify error is propagated correctly.
dataSource.enqueueError()
assertFailsWith<CustomException> {
- val errorParams = LoadParams(LoadType.END, key, 5, false, 5)
+ val errorParams = LoadParams(PageLoadType.END, key, 5, false, 5)
dataSource.load(errorParams)
}
}
@@ -269,9 +268,9 @@
override suspend fun load(params: LoadParams<Key>): LoadResult<Key, Item> {
return when (params.loadType) {
- LoadType.INITIAL -> loadInitial(params)
- LoadType.START -> loadBefore(params)
- LoadType.END -> loadAfter(params)
+ PageLoadType.REFRESH -> loadInitial(params)
+ PageLoadType.START -> loadBefore(params)
+ PageLoadType.END -> loadAfter(params)
}
}
diff --git a/paging/common/src/test/kotlin/androidx/paging/PagerTest.kt b/paging/common/src/test/kotlin/androidx/paging/PagerTest.kt
index 643b175..ed28898 100644
--- a/paging/common/src/test/kotlin/androidx/paging/PagerTest.kt
+++ b/paging/common/src/test/kotlin/androidx/paging/PagerTest.kt
@@ -16,11 +16,8 @@
package androidx.paging
-import androidx.paging.PagedList.LoadState.DONE
-import androidx.paging.PagedList.LoadState.IDLE
-import androidx.paging.PagedList.LoadState.LOADING
-import androidx.paging.PagedList.LoadType.END
-import androidx.paging.PagedList.LoadType.START
+import androidx.paging.PagedList.Config
+import androidx.paging.PagedList.LoadState
import androidx.paging.PagedSource.LoadResult
import androidx.paging.futures.DirectDispatcher
import androidx.testutils.TestExecutor
@@ -69,18 +66,11 @@
PositionalDataSource.RangeResult(data.subList(start, end)).toLoadResult<Int>()
private data class Result(
- val type: PagedList.LoadType,
+ val type: PageLoadType,
val pageResult: LoadResult<*, String>
)
- private data class StateChange(
- val type: PagedList.LoadType,
- val state: PagedList.LoadState,
- val error: Throwable? = null
- )
-
private class MockConsumer : Pager.PageConsumer<String> {
-
private val results: MutableList<Result> = arrayListOf()
private val stateChanges: MutableList<StateChange> = arrayListOf()
@@ -97,19 +87,15 @@
}
override fun onPageResult(
- type: PagedList.LoadType,
+ type: PageLoadType,
pageResult: LoadResult<*, String>
): Boolean {
results.add(Result(type, pageResult))
return false
}
- override fun onStateChanged(
- type: PagedList.LoadType,
- state: PagedList.LoadState,
- error: Throwable?
- ) {
- stateChanges.add(StateChange(type, state, error))
+ override fun onStateChanged(type: PageLoadType, state: LoadState) {
+ stateChanges.add(StateChange(type, state))
}
}
@@ -127,7 +113,7 @@
return Pager(
GlobalScope,
- PagedList.Config(2, 2, true, 10, PagedList.Config.MAX_SIZE_UNBOUNDED),
+ Config(2, 2, true, 10, Config.MAX_SIZE_UNBOUNDED),
PagedSourceWrapper(ImmediateListDataSource(data)),
DirectDispatcher,
DirectDispatcher,
@@ -148,18 +134,15 @@
assertTrue(consumer.takeResults().isEmpty())
assertEquals(
- consumer.takeStateChanges(),
- listOf(StateChange(END, LOADING))
+ listOf(StateChange(PageLoadType.END, LoadState.Loading)),
+ consumer.takeStateChanges()
)
testExecutor.executeAll()
+ assertEquals(listOf(Result(PageLoadType.END, rangeResult(6, 8))), consumer.takeResults())
assertEquals(
- listOf(Result(END, rangeResult(6, 8))),
- consumer.takeResults()
- )
- assertEquals(
- listOf(StateChange(END, IDLE)),
+ listOf(StateChange(PageLoadType.END, LoadState.Idle)),
consumer.takeStateChanges()
)
}
@@ -173,19 +156,15 @@
assertTrue(consumer.takeResults().isEmpty())
assertEquals(
- consumer.takeStateChanges(), listOf(
- StateChange(START, LOADING)
- )
+ listOf(StateChange(PageLoadType.START, LoadState.Loading)),
+ consumer.takeStateChanges()
)
testExecutor.executeAll()
+ assertEquals(listOf(Result(PageLoadType.START, rangeResult(2, 4))), consumer.takeResults())
assertEquals(
- listOf(Result(START, rangeResult(2, 4))),
- consumer.takeResults()
- )
- assertEquals(
- listOf(StateChange(START, IDLE)),
+ listOf(StateChange(PageLoadType.START, LoadState.Idle)),
consumer.takeStateChanges()
)
}
@@ -202,17 +181,18 @@
assertEquals(
listOf(
- Result(END, rangeResult(6, 8)),
- Result(END, rangeResult(8, 9))
+ Result(PageLoadType.END, rangeResult(6, 8)),
+ Result(PageLoadType.END, rangeResult(8, 9))
), consumer.takeResults()
)
assertEquals(
listOf(
- StateChange(END, LOADING),
- StateChange(END, IDLE),
- StateChange(END, LOADING),
- StateChange(END, IDLE)
- ), consumer.takeStateChanges()
+ StateChange(PageLoadType.END, LoadState.Loading),
+ StateChange(PageLoadType.END, LoadState.Idle),
+ StateChange(PageLoadType.END, LoadState.Loading),
+ StateChange(PageLoadType.END, LoadState.Idle)
+ ),
+ consumer.takeStateChanges()
)
}
@@ -228,17 +208,18 @@
assertEquals(
listOf(
- Result(START, rangeResult(2, 4)),
- Result(START, rangeResult(0, 2))
+ Result(PageLoadType.START, rangeResult(2, 4)),
+ Result(PageLoadType.START, rangeResult(0, 2))
), consumer.takeResults()
)
assertEquals(
listOf(
- StateChange(START, LOADING),
- StateChange(START, IDLE),
- StateChange(START, LOADING),
- StateChange(START, IDLE)
- ), consumer.takeStateChanges()
+ StateChange(PageLoadType.START, LoadState.Loading),
+ StateChange(PageLoadType.START, LoadState.Idle),
+ StateChange(PageLoadType.START, LoadState.Loading),
+ StateChange(PageLoadType.START, LoadState.Idle)
+ ),
+ consumer.takeStateChanges()
)
}
@@ -251,14 +232,12 @@
// Pager triggers an immediate empty response here, so we don't need to flush the executor
assertEquals(
- listOf(
- Result(END, LoadResult.empty<Int, String>())
- ), consumer.takeResults()
+ listOf(Result(PageLoadType.END, LoadResult.empty<Int, String>())),
+ consumer.takeResults()
)
assertEquals(
- listOf(
- StateChange(END, DONE)
- ), consumer.takeStateChanges()
+ listOf(StateChange(PageLoadType.END, LoadState.Done)),
+ consumer.takeStateChanges()
)
}
@@ -271,14 +250,12 @@
// Pager triggers an immediate empty response here, so we don't need to flush the executor
assertEquals(
- listOf(
- Result(START, LoadResult.empty<Int, String>())
- ), consumer.takeResults()
+ listOf(Result(PageLoadType.START, LoadResult.empty<Int, String>())),
+ consumer.takeResults()
)
assertEquals(
- listOf(
- StateChange(START, DONE)
- ), consumer.takeStateChanges()
+ listOf(StateChange(PageLoadType.START, LoadState.Done)),
+ consumer.takeStateChanges()
)
}
}
diff --git a/paging/common/src/test/kotlin/androidx/paging/StateChange.kt b/paging/common/src/test/kotlin/androidx/paging/StateChange.kt
new file mode 100644
index 0000000..c7fd2bc
--- /dev/null
+++ b/paging/common/src/test/kotlin/androidx/paging/StateChange.kt
@@ -0,0 +1,21 @@
+/*
+ * 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.paging
+
+import androidx.paging.PagedList.LoadState
+
+internal data class StateChange(val type: PageLoadType, val state: LoadState)
diff --git a/paging/integration-tests/testapp/src/main/java/androidx/paging/integration/testapp/custom/ItemDataSource.kt b/paging/integration-tests/testapp/src/main/java/androidx/paging/integration/testapp/custom/ItemDataSource.kt
index 857c84d..fa1a3b8 100644
--- a/paging/integration-tests/testapp/src/main/java/androidx/paging/integration/testapp/custom/ItemDataSource.kt
+++ b/paging/integration-tests/testapp/src/main/java/androidx/paging/integration/testapp/custom/ItemDataSource.kt
@@ -18,6 +18,7 @@
import android.graphics.Color
import androidx.annotation.ColorInt
+import androidx.paging.PageLoadType
import androidx.paging.PagedSource
import java.util.ArrayList
import java.util.concurrent.atomic.AtomicBoolean
@@ -31,7 +32,7 @@
override val keyProvider = KeyProvider.Positional
override suspend fun load(params: LoadParams<Int>) = when (params.loadType) {
- LoadType.INITIAL -> loadInitial(params)
+ PageLoadType.REFRESH -> loadInitial(params)
else -> loadRange(params)
}
diff --git a/paging/integration-tests/testapp/src/main/java/androidx/paging/integration/testapp/custom/PagedListSampleActivity.kt b/paging/integration-tests/testapp/src/main/java/androidx/paging/integration/testapp/custom/PagedListSampleActivity.kt
index d4ad393..063a4e5 100644
--- a/paging/integration-tests/testapp/src/main/java/androidx/paging/integration/testapp/custom/PagedListSampleActivity.kt
+++ b/paging/integration-tests/testapp/src/main/java/androidx/paging/integration/testapp/custom/PagedListSampleActivity.kt
@@ -21,7 +21,15 @@
import androidx.activity.viewModels
import androidx.appcompat.app.AppCompatActivity
import androidx.lifecycle.Observer
+import androidx.paging.PageLoadType
import androidx.paging.PagedList
+import androidx.paging.PagedList.LoadState.Done
+import androidx.paging.PagedList.LoadState.Error
+import androidx.paging.PagedList.LoadState.Idle
+import androidx.paging.PagedList.LoadState.Loading
+import androidx.paging.PageLoadType.END
+import androidx.paging.PageLoadType.REFRESH
+import androidx.paging.PageLoadType.START
import androidx.paging.PagedListAdapter
import androidx.paging.integration.testapp.R
import androidx.recyclerview.widget.RecyclerView
@@ -68,33 +76,29 @@
adapter.currentList?.retry()
}
- adapter.addLoadStateListener { type: PagedList.LoadType, state: PagedList.LoadState,
- _: Throwable? ->
+ adapter.addLoadStateListener { type: PageLoadType, state: PagedList.LoadState ->
val button = when (type) {
- PagedList.LoadType.REFRESH -> buttonRefresh
- PagedList.LoadType.START -> buttonStart
- PagedList.LoadType.END -> buttonEnd
+ REFRESH -> buttonRefresh
+ START -> buttonStart
+ END -> buttonEnd
}
+
when (state) {
- PagedList.LoadState.IDLE -> {
+ is Idle -> {
button.text = "Idle"
- button.isEnabled = type == PagedList.LoadType.REFRESH
+ button.isEnabled = type == REFRESH
}
- PagedList.LoadState.LOADING -> {
+ is Loading -> {
button.text = "Loading"
button.isEnabled = false
}
- PagedList.LoadState.DONE -> {
+ is Done -> {
button.text = "Done"
button.isEnabled = false
}
- PagedList.LoadState.ERROR -> {
+ is Error -> {
button.text = "Error"
- button.isEnabled = false
- }
- PagedList.LoadState.RETRYABLE_ERROR -> {
- button.text = "Error"
- button.isEnabled = true
+ button.isEnabled = state.retryable
}
}
}
diff --git a/paging/runtime/api/3.0.0-alpha01.txt b/paging/runtime/api/3.0.0-alpha01.txt
index 6822061..0a9333a 100644
--- a/paging/runtime/api/3.0.0-alpha01.txt
+++ b/paging/runtime/api/3.0.0-alpha01.txt
@@ -4,13 +4,13 @@
public class AsyncPagedListDiffer<T> {
ctor public AsyncPagedListDiffer(androidx.recyclerview.widget.RecyclerView.Adapter<?> adapter, androidx.recyclerview.widget.DiffUtil.ItemCallback<T> diffCallback);
ctor public AsyncPagedListDiffer(androidx.recyclerview.widget.ListUpdateCallback listUpdateCallback, androidx.recyclerview.widget.AsyncDifferConfig<T> config);
- method public void addLoadStateListener(kotlin.jvm.functions.Function3<? super androidx.paging.PagedList.LoadType,? super androidx.paging.PagedList.LoadState,? super java.lang.Throwable,kotlin.Unit> listener);
+ method public void addLoadStateListener(kotlin.jvm.functions.Function2<? super androidx.paging.PageLoadType,? super androidx.paging.PagedList.LoadState,kotlin.Unit> listener);
method public void addPagedListListener(androidx.paging.AsyncPagedListDiffer.PagedListListener<T> listener);
method public final void addPagedListListener(kotlin.jvm.functions.Function2<? super androidx.paging.PagedList<T>,? super androidx.paging.PagedList<T>,kotlin.Unit> callback);
method public androidx.paging.PagedList<T>? getCurrentList();
method public T? getItem(int index);
method public int getItemCount();
- method public void removeLoadStateListener(kotlin.jvm.functions.Function3<? super androidx.paging.PagedList.LoadType,? super androidx.paging.PagedList.LoadState,? super java.lang.Throwable,kotlin.Unit> listener);
+ method public void removeLoadStateListener(kotlin.jvm.functions.Function2<? super androidx.paging.PageLoadType,? super androidx.paging.PagedList.LoadState,kotlin.Unit> listener);
method public void removePagedListListener(androidx.paging.AsyncPagedListDiffer.PagedListListener<T> listener);
method public final void removePagedListListener(kotlin.jvm.functions.Function2<? super androidx.paging.PagedList<T>,? super androidx.paging.PagedList<T>,kotlin.Unit> callback);
method public void submitList(androidx.paging.PagedList<T>? pagedList);
@@ -50,14 +50,14 @@
public abstract class PagedListAdapter<T, VH extends androidx.recyclerview.widget.RecyclerView.ViewHolder> extends androidx.recyclerview.widget.RecyclerView.Adapter<VH> {
ctor protected PagedListAdapter(androidx.recyclerview.widget.DiffUtil.ItemCallback<T> diffCallback);
ctor protected PagedListAdapter(androidx.recyclerview.widget.AsyncDifferConfig<T> config);
- method public void addLoadStateListener(kotlin.jvm.functions.Function3<? super androidx.paging.PagedList.LoadType,? super androidx.paging.PagedList.LoadState,? super java.lang.Throwable,kotlin.Unit> callback);
+ method public void addLoadStateListener(kotlin.jvm.functions.Function2<? super androidx.paging.PageLoadType,? super androidx.paging.PagedList.LoadState,kotlin.Unit> callback);
method public androidx.paging.PagedList<T>? getCurrentList();
method protected T? getItem(int position);
method public int getItemCount();
method @Deprecated public void onCurrentListChanged(androidx.paging.PagedList<T>? currentList);
method public void onCurrentListChanged(androidx.paging.PagedList<T>? previousList, androidx.paging.PagedList<T>? currentList);
- method public void onLoadStateChanged(androidx.paging.PagedList.LoadType type, androidx.paging.PagedList.LoadState state, Throwable? error);
- method public void removeLoadStateListener(kotlin.jvm.functions.Function3<? super androidx.paging.PagedList.LoadType,? super androidx.paging.PagedList.LoadState,? super java.lang.Throwable,kotlin.Unit> callback);
+ method public void onLoadStateChanged(androidx.paging.PageLoadType type, androidx.paging.PagedList.LoadState state);
+ method public void removeLoadStateListener(kotlin.jvm.functions.Function2<? super androidx.paging.PageLoadType,? super androidx.paging.PagedList.LoadState,kotlin.Unit> callback);
method public void submitList(androidx.paging.PagedList<T>? pagedList);
method public void submitList(androidx.paging.PagedList<T>? pagedList, Runnable? commitCallback);
property public androidx.paging.PagedList<T>? currentList;
diff --git a/paging/runtime/api/current.txt b/paging/runtime/api/current.txt
index 6822061..0a9333a 100644
--- a/paging/runtime/api/current.txt
+++ b/paging/runtime/api/current.txt
@@ -4,13 +4,13 @@
public class AsyncPagedListDiffer<T> {
ctor public AsyncPagedListDiffer(androidx.recyclerview.widget.RecyclerView.Adapter<?> adapter, androidx.recyclerview.widget.DiffUtil.ItemCallback<T> diffCallback);
ctor public AsyncPagedListDiffer(androidx.recyclerview.widget.ListUpdateCallback listUpdateCallback, androidx.recyclerview.widget.AsyncDifferConfig<T> config);
- method public void addLoadStateListener(kotlin.jvm.functions.Function3<? super androidx.paging.PagedList.LoadType,? super androidx.paging.PagedList.LoadState,? super java.lang.Throwable,kotlin.Unit> listener);
+ method public void addLoadStateListener(kotlin.jvm.functions.Function2<? super androidx.paging.PageLoadType,? super androidx.paging.PagedList.LoadState,kotlin.Unit> listener);
method public void addPagedListListener(androidx.paging.AsyncPagedListDiffer.PagedListListener<T> listener);
method public final void addPagedListListener(kotlin.jvm.functions.Function2<? super androidx.paging.PagedList<T>,? super androidx.paging.PagedList<T>,kotlin.Unit> callback);
method public androidx.paging.PagedList<T>? getCurrentList();
method public T? getItem(int index);
method public int getItemCount();
- method public void removeLoadStateListener(kotlin.jvm.functions.Function3<? super androidx.paging.PagedList.LoadType,? super androidx.paging.PagedList.LoadState,? super java.lang.Throwable,kotlin.Unit> listener);
+ method public void removeLoadStateListener(kotlin.jvm.functions.Function2<? super androidx.paging.PageLoadType,? super androidx.paging.PagedList.LoadState,kotlin.Unit> listener);
method public void removePagedListListener(androidx.paging.AsyncPagedListDiffer.PagedListListener<T> listener);
method public final void removePagedListListener(kotlin.jvm.functions.Function2<? super androidx.paging.PagedList<T>,? super androidx.paging.PagedList<T>,kotlin.Unit> callback);
method public void submitList(androidx.paging.PagedList<T>? pagedList);
@@ -50,14 +50,14 @@
public abstract class PagedListAdapter<T, VH extends androidx.recyclerview.widget.RecyclerView.ViewHolder> extends androidx.recyclerview.widget.RecyclerView.Adapter<VH> {
ctor protected PagedListAdapter(androidx.recyclerview.widget.DiffUtil.ItemCallback<T> diffCallback);
ctor protected PagedListAdapter(androidx.recyclerview.widget.AsyncDifferConfig<T> config);
- method public void addLoadStateListener(kotlin.jvm.functions.Function3<? super androidx.paging.PagedList.LoadType,? super androidx.paging.PagedList.LoadState,? super java.lang.Throwable,kotlin.Unit> callback);
+ method public void addLoadStateListener(kotlin.jvm.functions.Function2<? super androidx.paging.PageLoadType,? super androidx.paging.PagedList.LoadState,kotlin.Unit> callback);
method public androidx.paging.PagedList<T>? getCurrentList();
method protected T? getItem(int position);
method public int getItemCount();
method @Deprecated public void onCurrentListChanged(androidx.paging.PagedList<T>? currentList);
method public void onCurrentListChanged(androidx.paging.PagedList<T>? previousList, androidx.paging.PagedList<T>? currentList);
- method public void onLoadStateChanged(androidx.paging.PagedList.LoadType type, androidx.paging.PagedList.LoadState state, Throwable? error);
- method public void removeLoadStateListener(kotlin.jvm.functions.Function3<? super androidx.paging.PagedList.LoadType,? super androidx.paging.PagedList.LoadState,? super java.lang.Throwable,kotlin.Unit> callback);
+ method public void onLoadStateChanged(androidx.paging.PageLoadType type, androidx.paging.PagedList.LoadState state);
+ method public void removeLoadStateListener(kotlin.jvm.functions.Function2<? super androidx.paging.PageLoadType,? super androidx.paging.PagedList.LoadState,kotlin.Unit> callback);
method public void submitList(androidx.paging.PagedList<T>? pagedList);
method public void submitList(androidx.paging.PagedList<T>? pagedList, Runnable? commitCallback);
property public androidx.paging.PagedList<T>? currentList;
diff --git a/paging/runtime/api/restricted_3.0.0-alpha01.txt b/paging/runtime/api/restricted_3.0.0-alpha01.txt
index 6822061..0a9333a 100644
--- a/paging/runtime/api/restricted_3.0.0-alpha01.txt
+++ b/paging/runtime/api/restricted_3.0.0-alpha01.txt
@@ -4,13 +4,13 @@
public class AsyncPagedListDiffer<T> {
ctor public AsyncPagedListDiffer(androidx.recyclerview.widget.RecyclerView.Adapter<?> adapter, androidx.recyclerview.widget.DiffUtil.ItemCallback<T> diffCallback);
ctor public AsyncPagedListDiffer(androidx.recyclerview.widget.ListUpdateCallback listUpdateCallback, androidx.recyclerview.widget.AsyncDifferConfig<T> config);
- method public void addLoadStateListener(kotlin.jvm.functions.Function3<? super androidx.paging.PagedList.LoadType,? super androidx.paging.PagedList.LoadState,? super java.lang.Throwable,kotlin.Unit> listener);
+ method public void addLoadStateListener(kotlin.jvm.functions.Function2<? super androidx.paging.PageLoadType,? super androidx.paging.PagedList.LoadState,kotlin.Unit> listener);
method public void addPagedListListener(androidx.paging.AsyncPagedListDiffer.PagedListListener<T> listener);
method public final void addPagedListListener(kotlin.jvm.functions.Function2<? super androidx.paging.PagedList<T>,? super androidx.paging.PagedList<T>,kotlin.Unit> callback);
method public androidx.paging.PagedList<T>? getCurrentList();
method public T? getItem(int index);
method public int getItemCount();
- method public void removeLoadStateListener(kotlin.jvm.functions.Function3<? super androidx.paging.PagedList.LoadType,? super androidx.paging.PagedList.LoadState,? super java.lang.Throwable,kotlin.Unit> listener);
+ method public void removeLoadStateListener(kotlin.jvm.functions.Function2<? super androidx.paging.PageLoadType,? super androidx.paging.PagedList.LoadState,kotlin.Unit> listener);
method public void removePagedListListener(androidx.paging.AsyncPagedListDiffer.PagedListListener<T> listener);
method public final void removePagedListListener(kotlin.jvm.functions.Function2<? super androidx.paging.PagedList<T>,? super androidx.paging.PagedList<T>,kotlin.Unit> callback);
method public void submitList(androidx.paging.PagedList<T>? pagedList);
@@ -50,14 +50,14 @@
public abstract class PagedListAdapter<T, VH extends androidx.recyclerview.widget.RecyclerView.ViewHolder> extends androidx.recyclerview.widget.RecyclerView.Adapter<VH> {
ctor protected PagedListAdapter(androidx.recyclerview.widget.DiffUtil.ItemCallback<T> diffCallback);
ctor protected PagedListAdapter(androidx.recyclerview.widget.AsyncDifferConfig<T> config);
- method public void addLoadStateListener(kotlin.jvm.functions.Function3<? super androidx.paging.PagedList.LoadType,? super androidx.paging.PagedList.LoadState,? super java.lang.Throwable,kotlin.Unit> callback);
+ method public void addLoadStateListener(kotlin.jvm.functions.Function2<? super androidx.paging.PageLoadType,? super androidx.paging.PagedList.LoadState,kotlin.Unit> callback);
method public androidx.paging.PagedList<T>? getCurrentList();
method protected T? getItem(int position);
method public int getItemCount();
method @Deprecated public void onCurrentListChanged(androidx.paging.PagedList<T>? currentList);
method public void onCurrentListChanged(androidx.paging.PagedList<T>? previousList, androidx.paging.PagedList<T>? currentList);
- method public void onLoadStateChanged(androidx.paging.PagedList.LoadType type, androidx.paging.PagedList.LoadState state, Throwable? error);
- method public void removeLoadStateListener(kotlin.jvm.functions.Function3<? super androidx.paging.PagedList.LoadType,? super androidx.paging.PagedList.LoadState,? super java.lang.Throwable,kotlin.Unit> callback);
+ method public void onLoadStateChanged(androidx.paging.PageLoadType type, androidx.paging.PagedList.LoadState state);
+ method public void removeLoadStateListener(kotlin.jvm.functions.Function2<? super androidx.paging.PageLoadType,? super androidx.paging.PagedList.LoadState,kotlin.Unit> callback);
method public void submitList(androidx.paging.PagedList<T>? pagedList);
method public void submitList(androidx.paging.PagedList<T>? pagedList, Runnable? commitCallback);
property public androidx.paging.PagedList<T>? currentList;
diff --git a/paging/runtime/api/restricted_current.txt b/paging/runtime/api/restricted_current.txt
index 6822061..0a9333a 100644
--- a/paging/runtime/api/restricted_current.txt
+++ b/paging/runtime/api/restricted_current.txt
@@ -4,13 +4,13 @@
public class AsyncPagedListDiffer<T> {
ctor public AsyncPagedListDiffer(androidx.recyclerview.widget.RecyclerView.Adapter<?> adapter, androidx.recyclerview.widget.DiffUtil.ItemCallback<T> diffCallback);
ctor public AsyncPagedListDiffer(androidx.recyclerview.widget.ListUpdateCallback listUpdateCallback, androidx.recyclerview.widget.AsyncDifferConfig<T> config);
- method public void addLoadStateListener(kotlin.jvm.functions.Function3<? super androidx.paging.PagedList.LoadType,? super androidx.paging.PagedList.LoadState,? super java.lang.Throwable,kotlin.Unit> listener);
+ method public void addLoadStateListener(kotlin.jvm.functions.Function2<? super androidx.paging.PageLoadType,? super androidx.paging.PagedList.LoadState,kotlin.Unit> listener);
method public void addPagedListListener(androidx.paging.AsyncPagedListDiffer.PagedListListener<T> listener);
method public final void addPagedListListener(kotlin.jvm.functions.Function2<? super androidx.paging.PagedList<T>,? super androidx.paging.PagedList<T>,kotlin.Unit> callback);
method public androidx.paging.PagedList<T>? getCurrentList();
method public T? getItem(int index);
method public int getItemCount();
- method public void removeLoadStateListener(kotlin.jvm.functions.Function3<? super androidx.paging.PagedList.LoadType,? super androidx.paging.PagedList.LoadState,? super java.lang.Throwable,kotlin.Unit> listener);
+ method public void removeLoadStateListener(kotlin.jvm.functions.Function2<? super androidx.paging.PageLoadType,? super androidx.paging.PagedList.LoadState,kotlin.Unit> listener);
method public void removePagedListListener(androidx.paging.AsyncPagedListDiffer.PagedListListener<T> listener);
method public final void removePagedListListener(kotlin.jvm.functions.Function2<? super androidx.paging.PagedList<T>,? super androidx.paging.PagedList<T>,kotlin.Unit> callback);
method public void submitList(androidx.paging.PagedList<T>? pagedList);
@@ -50,14 +50,14 @@
public abstract class PagedListAdapter<T, VH extends androidx.recyclerview.widget.RecyclerView.ViewHolder> extends androidx.recyclerview.widget.RecyclerView.Adapter<VH> {
ctor protected PagedListAdapter(androidx.recyclerview.widget.DiffUtil.ItemCallback<T> diffCallback);
ctor protected PagedListAdapter(androidx.recyclerview.widget.AsyncDifferConfig<T> config);
- method public void addLoadStateListener(kotlin.jvm.functions.Function3<? super androidx.paging.PagedList.LoadType,? super androidx.paging.PagedList.LoadState,? super java.lang.Throwable,kotlin.Unit> callback);
+ method public void addLoadStateListener(kotlin.jvm.functions.Function2<? super androidx.paging.PageLoadType,? super androidx.paging.PagedList.LoadState,kotlin.Unit> callback);
method public androidx.paging.PagedList<T>? getCurrentList();
method protected T? getItem(int position);
method public int getItemCount();
method @Deprecated public void onCurrentListChanged(androidx.paging.PagedList<T>? currentList);
method public void onCurrentListChanged(androidx.paging.PagedList<T>? previousList, androidx.paging.PagedList<T>? currentList);
- method public void onLoadStateChanged(androidx.paging.PagedList.LoadType type, androidx.paging.PagedList.LoadState state, Throwable? error);
- method public void removeLoadStateListener(kotlin.jvm.functions.Function3<? super androidx.paging.PagedList.LoadType,? super androidx.paging.PagedList.LoadState,? super java.lang.Throwable,kotlin.Unit> callback);
+ method public void onLoadStateChanged(androidx.paging.PageLoadType type, androidx.paging.PagedList.LoadState state);
+ method public void removeLoadStateListener(kotlin.jvm.functions.Function2<? super androidx.paging.PageLoadType,? super androidx.paging.PagedList.LoadState,kotlin.Unit> callback);
method public void submitList(androidx.paging.PagedList<T>? pagedList);
method public void submitList(androidx.paging.PagedList<T>? pagedList, Runnable? commitCallback);
property public androidx.paging.PagedList<T>? currentList;
diff --git a/paging/runtime/src/androidTest/java/androidx/paging/LivePagedListBuilderTest.kt b/paging/runtime/src/androidTest/java/androidx/paging/LivePagedListBuilderTest.kt
index c8e43b1..9ac83e2 100644
--- a/paging/runtime/src/androidTest/java/androidx/paging/LivePagedListBuilderTest.kt
+++ b/paging/runtime/src/androidTest/java/androidx/paging/LivePagedListBuilderTest.kt
@@ -22,10 +22,10 @@
import androidx.lifecycle.LifecycleOwner
import androidx.lifecycle.LifecycleRegistry
import androidx.lifecycle.Observer
-import androidx.paging.PagedList.LoadState.IDLE
-import androidx.paging.PagedList.LoadState.LOADING
-import androidx.paging.PagedList.LoadState.RETRYABLE_ERROR
-import androidx.paging.PagedList.LoadType.REFRESH
+import androidx.paging.PagedList.LoadState.Error
+import androidx.paging.PagedList.LoadState.Idle
+import androidx.paging.PagedList.LoadState.Loading
+import androidx.paging.PageLoadType.REFRESH
import androidx.test.filters.SmallTest
import androidx.testutils.TestDispatcher
import androidx.testutils.TestExecutor
@@ -63,6 +63,11 @@
}
}
+ private data class LoadState(
+ val type: PageLoadType,
+ val state: PagedList.LoadState
+ )
+
@ExperimentalCoroutinesApi
@Before
fun setup() {
@@ -106,7 +111,7 @@
override val keyProvider = KeyProvider.Positional
override suspend fun load(params: LoadParams<Int>) = when (params.loadType) {
- LoadType.INITIAL -> loadInitial(params)
+ PageLoadType.REFRESH -> loadInitial(params)
else -> loadRange()
}
@@ -166,12 +171,6 @@
assertEquals(listOf("a", "b", "c", "d"), pagedList)
}
- data class LoadState(
- val type: PagedList.LoadType,
- val state: PagedList.LoadState,
- val error: Throwable?
- )
-
@Test
fun failedLoad() {
val factory = MockDataSourceFactory()
@@ -194,9 +193,9 @@
assertNotNull(initPagedList!!)
assertTrue(initPagedList is InitialPagedList<*, *>)
- val loadStateChangedCallback: LoadStateListener = { type, state, error ->
+ val loadStateChangedCallback: LoadStateListener = { type, state ->
if (type == REFRESH) {
- loadStates.add(LoadState(type, state, error))
+ loadStates.add(LoadState(type, state))
}
}
initPagedList.addWeakLoadStateListener(loadStateChangedCallback)
@@ -208,9 +207,9 @@
// TODO: Investigate removing initial IDLE state from callback updates.
assertEquals(
listOf(
- LoadState(REFRESH, IDLE, null),
- LoadState(REFRESH, LOADING, null),
- LoadState(REFRESH, RETRYABLE_ERROR, RETRYABLE_EXCEPTION)
+ LoadState(REFRESH, Idle),
+ LoadState(REFRESH, Loading),
+ LoadState(REFRESH, Error(RETRYABLE_EXCEPTION, true))
), loadStates
)
@@ -225,10 +224,10 @@
assertEquals(
listOf(
- LoadState(REFRESH, IDLE, null),
- LoadState(REFRESH, LOADING, null),
- LoadState(REFRESH, RETRYABLE_ERROR, RETRYABLE_EXCEPTION),
- LoadState(REFRESH, LOADING, null)
+ LoadState(REFRESH, Idle),
+ LoadState(REFRESH, Loading),
+ LoadState(REFRESH, Error(RETRYABLE_EXCEPTION, true)),
+ LoadState(REFRESH, Loading)
), loadStates
)
@@ -237,12 +236,13 @@
pagedListHolder[0]!!.addWeakLoadStateListener(loadStateChangedCallback)
assertEquals(
listOf(
- LoadState(REFRESH, IDLE, null),
- LoadState(REFRESH, LOADING, null),
- LoadState(REFRESH, RETRYABLE_ERROR, RETRYABLE_EXCEPTION),
- LoadState(REFRESH, LOADING, null),
- LoadState(REFRESH, IDLE, null)
- ), loadStates
+ LoadState(REFRESH, Idle),
+ LoadState(REFRESH, Loading),
+ LoadState(REFRESH, Error(RETRYABLE_EXCEPTION, true)),
+ LoadState(REFRESH, Loading),
+ LoadState(REFRESH, Idle)
+ ),
+ loadStates
)
}
diff --git a/paging/runtime/src/main/java/androidx/paging/AsyncPagedListDiffer.kt b/paging/runtime/src/main/java/androidx/paging/AsyncPagedListDiffer.kt
index 39754e2..506e5dc 100644
--- a/paging/runtime/src/main/java/androidx/paging/AsyncPagedListDiffer.kt
+++ b/paging/runtime/src/main/java/androidx/paging/AsyncPagedListDiffer.kt
@@ -21,7 +21,6 @@
import androidx.lifecycle.LiveData
import androidx.paging.PagedList.LoadState
import androidx.paging.PagedList.LoadStateManager
-import androidx.paging.PagedList.LoadType
import androidx.recyclerview.widget.AdapterListUpdateCallback
import androidx.recyclerview.widget.AsyncDifferConfig
import androidx.recyclerview.widget.DiffUtil
@@ -141,9 +140,9 @@
internal var maxScheduledGeneration: Int = 0
private val loadStateManager: LoadStateManager = object : LoadStateManager() {
- override fun onStateChanged(type: LoadType, state: LoadState, error: Throwable?) {
+ override fun onStateChanged(type: PageLoadType, state: LoadState) {
// Don't need to post - PagedList will already have done that
- loadStateListeners.forEach { it(type, state, error) }
+ loadStateListeners.forEach { it(type, state) }
}
}
@@ -278,7 +277,7 @@
*
* @param pagedList The new [PagedList].
* @param commitCallback Optional runnable that is executed when the PagedList is committed, if
- * it is committed.
+ * it is committed.
*/
open fun submitList(pagedList: PagedList<T>?, commitCallback: Runnable?) {
// incrementing generation means any currently-running diffs are discarded when they finish
@@ -401,7 +400,7 @@
// This is a load, not just an update of last load position, since the new list may be
// incomplete. If new list is subset of old list, but doesn't fill the viewport, this
// will likely trigger a load of new data.
- newList.loadAround(Math.max(0, Math.min(newList.size - 1, newPosition)))
+ newList.loadAround(newPosition.coerceIn(0, newList.size - 1))
}
onCurrentListChanged(previousSnapshot, pagedList, commitCallback)
diff --git a/paging/runtime/src/main/java/androidx/paging/LivePagedList.kt b/paging/runtime/src/main/java/androidx/paging/LivePagedList.kt
index 2a0a7e4..c6a5582 100644
--- a/paging/runtime/src/main/java/androidx/paging/LivePagedList.kt
+++ b/paging/runtime/src/main/java/androidx/paging/LivePagedList.kt
@@ -18,6 +18,9 @@
import androidx.arch.core.executor.ArchTaskExecutor
import androidx.lifecycle.LiveData
+import androidx.paging.PageLoadType.REFRESH
+import androidx.paging.PagedList.LoadState.Error
+import androidx.paging.PagedList.LoadState.Loading
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
@@ -59,13 +62,8 @@
}
private fun onError(throwable: Throwable) {
- val loadState = if (currentData.pagedSource.isRetryableError(throwable)) {
- PagedList.LoadState.RETRYABLE_ERROR
- } else {
- PagedList.LoadState.ERROR
- }
-
- currentData.setInitialLoadState(loadState, throwable)
+ val loadState = Error(throwable, (currentData.pagedSource.isRetryableError(throwable)))
+ currentData.setInitialLoadState(REFRESH, loadState)
}
private fun onSuccess(value: PagedList<Value>) {
@@ -104,7 +102,7 @@
pagedSource.registerInvalidatedCallback(callback)
withContext(notifyDispatcher) {
- currentData.setInitialLoadState(PagedList.LoadState.LOADING, null)
+ currentData.setInitialLoadState(REFRESH, Loading)
}
@Suppress("UNCHECKED_CAST") // getLastKey guaranteed to be of 'Key' type
diff --git a/paging/runtime/src/main/java/androidx/paging/LivePagedListBuilder.kt b/paging/runtime/src/main/java/androidx/paging/LivePagedListBuilder.kt
index a374d4a..5e39972 100644
--- a/paging/runtime/src/main/java/androidx/paging/LivePagedListBuilder.kt
+++ b/paging/runtime/src/main/java/androidx/paging/LivePagedListBuilder.kt
@@ -30,8 +30,8 @@
* The required parameters are in the constructor, so you can simply construct and build, or
* optionally enable extra features (such as initial load key, or BoundaryCallback).
*
- * @param Key Type of input valued used to load data from the [DataSource]. Must be integer if
- * you're using [PositionalDataSource].
+ * @param Key Type of input valued used to load data from the [DataSource]. Must be [Int] if you're
+ * using [PositionalDataSource].
* @param Value Item type being presented.
*
* @see toLiveData
diff --git a/paging/runtime/src/main/java/androidx/paging/PagedListAdapter.kt b/paging/runtime/src/main/java/androidx/paging/PagedListAdapter.kt
index a40db41..418fd3e 100644
--- a/paging/runtime/src/main/java/androidx/paging/PagedListAdapter.kt
+++ b/paging/runtime/src/main/java/androidx/paging/PagedListAdapter.kt
@@ -17,7 +17,6 @@
package androidx.paging
import androidx.paging.PagedList.LoadState
-import androidx.paging.PagedList.LoadType
import androidx.recyclerview.widget.AdapterListUpdateCallback
import androidx.recyclerview.widget.AsyncDifferConfig
import androidx.recyclerview.widget.DiffUtil
@@ -173,7 +172,7 @@
*
* @param pagedList The new list to be displayed.
* @param commitCallback Optional runnable that is executed when the PagedList is committed, if
- * it is committed.
+ * it is committed.
*/
open fun submitList(pagedList: PagedList<T>?, commitCallback: Runnable?) =
differ.submitList(pagedList, commitCallback)
@@ -217,8 +216,8 @@
* to a snapshot version of the PagedList during a diff. This means you cannot observe each
* PagedList via this method.
*
- * @param previousList PagedList that was previously displayed, may be null.
- * @param currentList new PagedList being displayed, may be null.
+ * @param previousList [PagedList] that was previously displayed, may be null.
+ * @param currentList new [PagedList] being displayed, may be null.
*
* @see currentList
*/
@@ -226,17 +225,15 @@
}
/**
- * Called when the LoadState for a particular type of load (START, END, REFRESH) has
- * changed.
+ * Called when the [LoadState] for a particular type of load (START, END, REFRESH) has changed.
*
* REFRESH events can be used to drive a `SwipeRefreshLayout`, or START/END events
* can be used to drive loading spinner items in the Adapter.
*
- * @param type Type of load - START, END, or REFRESH.
- * @param state State of load - IDLE, LOADING, DONE, ERROR, or RETRYABLE_ERROR
- * @param error Error, if in an error state, null otherwise.
+ * @param type [PageLoadType] Can be START, END, or REFRESH
+ * @param state [LoadState] IDLE, LOADING, DONE, ERROR, or RETRYABLE_ERROR
*/
- open fun onLoadStateChanged(type: LoadType, state: LoadState, error: Throwable?) {
+ open fun onLoadStateChanged(type: PageLoadType, state: LoadState) {
}
/**
diff --git a/paging/rxjava2/src/main/java/androidx/paging/RxPagedListBuilder.kt b/paging/rxjava2/src/main/java/androidx/paging/RxPagedListBuilder.kt
index c435147..f44b598 100644
--- a/paging/rxjava2/src/main/java/androidx/paging/RxPagedListBuilder.kt
+++ b/paging/rxjava2/src/main/java/androidx/paging/RxPagedListBuilder.kt
@@ -39,11 +39,11 @@
* perform all loading on that scheduler. It will already be observed on [setNotifyScheduler], and
* will dispatch new PagedLists, as well as their updates to that scheduler.
*
- * @param Key Type of input valued used to load data from the DataSource. Must be integer if you're
- * using PositionalDataSource.
+ * @param Key Type of input valued used to load data from the [DataSource]. Must be integer if
+ * you're using [PositionalDataSource].
* @param Value Item type being presented.
*
- * @constructor Creates a RxPagedListBuilder with required parameters.
+ * @constructor Creates a [RxPagedListBuilder] with required parameters.
* @param dataSourceFactory DataSource factory providing DataSource generations.
* @param config Paging configuration.
*/
diff --git a/palette/palette/build.gradle b/palette/palette/build.gradle
index 1a785db..87eb258b 100644
--- a/palette/palette/build.gradle
+++ b/palette/palette/build.gradle
@@ -9,7 +9,7 @@
}
dependencies {
- api("androidx.core:core:1.1.0-rc01")
+ api("androidx.core:core:1.1.0")
implementation("androidx.collection:collection:1.1.0")
annotationProcessor(NULLAWAY)
diff --git a/percentlayout/percentlayout/build.gradle b/percentlayout/percentlayout/build.gradle
index 5b2ac05..e5c8470 100644
--- a/percentlayout/percentlayout/build.gradle
+++ b/percentlayout/percentlayout/build.gradle
@@ -10,7 +10,7 @@
dependencies {
api(ANDROIDX_ANNOTATION)
- implementation("androidx.core:core:1.1.0-rc01")
+ implementation("androidx.core:core:1.1.0")
androidTestImplementation(ANDROIDX_TEST_EXT_JUNIT)
androidTestImplementation(ANDROIDX_TEST_CORE)
diff --git a/preference/ktx/OWNERS b/preference/preference-ktx/OWNERS
similarity index 100%
rename from preference/ktx/OWNERS
rename to preference/preference-ktx/OWNERS
diff --git a/preference/ktx/api/1.0.0.txt b/preference/preference-ktx/api/1.0.0.txt
similarity index 100%
rename from preference/ktx/api/1.0.0.txt
rename to preference/preference-ktx/api/1.0.0.txt
diff --git a/preference/ktx/api/1.1.0-alpha02.txt b/preference/preference-ktx/api/1.1.0-alpha02.txt
similarity index 100%
rename from preference/ktx/api/1.1.0-alpha02.txt
rename to preference/preference-ktx/api/1.1.0-alpha02.txt
diff --git a/preference/ktx/api/1.1.0-alpha03.txt b/preference/preference-ktx/api/1.1.0-alpha03.txt
similarity index 100%
rename from preference/ktx/api/1.1.0-alpha03.txt
rename to preference/preference-ktx/api/1.1.0-alpha03.txt
diff --git a/preference/ktx/api/1.1.0-alpha04.txt b/preference/preference-ktx/api/1.1.0-alpha04.txt
similarity index 100%
rename from preference/ktx/api/1.1.0-alpha04.txt
rename to preference/preference-ktx/api/1.1.0-alpha04.txt
diff --git a/preference/ktx/api/1.1.0-alpha05.txt b/preference/preference-ktx/api/1.1.0-alpha05.txt
similarity index 100%
rename from preference/ktx/api/1.1.0-alpha05.txt
rename to preference/preference-ktx/api/1.1.0-alpha05.txt
diff --git a/preference/ktx/api/1.1.0-alpha06.txt b/preference/preference-ktx/api/1.1.0-alpha06.txt
similarity index 100%
rename from preference/ktx/api/1.1.0-alpha06.txt
rename to preference/preference-ktx/api/1.1.0-alpha06.txt
diff --git a/preference/ktx/api/1.1.0-beta01.txt b/preference/preference-ktx/api/1.1.0-beta01.txt
similarity index 100%
rename from preference/ktx/api/1.1.0-beta01.txt
rename to preference/preference-ktx/api/1.1.0-beta01.txt
diff --git a/preference/ktx/api/1.1.0-beta02.txt b/preference/preference-ktx/api/1.1.0-beta02.txt
similarity index 100%
rename from preference/ktx/api/1.1.0-beta02.txt
rename to preference/preference-ktx/api/1.1.0-beta02.txt
diff --git a/preference/ktx/api/1.1.0-rc01.txt b/preference/preference-ktx/api/1.1.0-rc01.txt
similarity index 100%
rename from preference/ktx/api/1.1.0-rc01.txt
rename to preference/preference-ktx/api/1.1.0-rc01.txt
diff --git a/preference/ktx/api/1.2.0-alpha01.txt b/preference/preference-ktx/api/1.2.0-alpha01.txt
similarity index 100%
rename from preference/ktx/api/1.2.0-alpha01.txt
rename to preference/preference-ktx/api/1.2.0-alpha01.txt
diff --git a/preference/ktx/api/api_lint.ignore b/preference/preference-ktx/api/api_lint.ignore
similarity index 100%
rename from preference/ktx/api/api_lint.ignore
rename to preference/preference-ktx/api/api_lint.ignore
diff --git a/preference/ktx/api/current.txt b/preference/preference-ktx/api/current.txt
similarity index 100%
rename from preference/ktx/api/current.txt
rename to preference/preference-ktx/api/current.txt
diff --git a/preference/ktx/api/res-1.0.0.txt b/preference/preference-ktx/api/res-1.0.0.txt
similarity index 100%
rename from preference/ktx/api/res-1.0.0.txt
rename to preference/preference-ktx/api/res-1.0.0.txt
diff --git a/preference/ktx/api/res-1.1.0-alpha02.txt b/preference/preference-ktx/api/res-1.1.0-alpha02.txt
similarity index 100%
rename from preference/ktx/api/res-1.1.0-alpha02.txt
rename to preference/preference-ktx/api/res-1.1.0-alpha02.txt
diff --git a/preference/ktx/api/res-1.1.0-alpha03.txt b/preference/preference-ktx/api/res-1.1.0-alpha03.txt
similarity index 100%
rename from preference/ktx/api/res-1.1.0-alpha03.txt
rename to preference/preference-ktx/api/res-1.1.0-alpha03.txt
diff --git a/preference/ktx/api/res-1.1.0-alpha04.txt b/preference/preference-ktx/api/res-1.1.0-alpha04.txt
similarity index 100%
rename from preference/ktx/api/res-1.1.0-alpha04.txt
rename to preference/preference-ktx/api/res-1.1.0-alpha04.txt
diff --git a/preference/ktx/api/res-1.1.0-alpha05.txt b/preference/preference-ktx/api/res-1.1.0-alpha05.txt
similarity index 100%
rename from preference/ktx/api/res-1.1.0-alpha05.txt
rename to preference/preference-ktx/api/res-1.1.0-alpha05.txt
diff --git a/preference/ktx/api/res-1.1.0-alpha06.txt b/preference/preference-ktx/api/res-1.1.0-alpha06.txt
similarity index 100%
rename from preference/ktx/api/res-1.1.0-alpha06.txt
rename to preference/preference-ktx/api/res-1.1.0-alpha06.txt
diff --git a/preference/ktx/api/res-1.1.0-beta01.txt b/preference/preference-ktx/api/res-1.1.0-beta01.txt
similarity index 100%
rename from preference/ktx/api/res-1.1.0-beta01.txt
rename to preference/preference-ktx/api/res-1.1.0-beta01.txt
diff --git a/preference/ktx/api/res-1.1.0-beta02.txt b/preference/preference-ktx/api/res-1.1.0-beta02.txt
similarity index 100%
rename from preference/ktx/api/res-1.1.0-beta02.txt
rename to preference/preference-ktx/api/res-1.1.0-beta02.txt
diff --git a/preference/ktx/api/res-1.1.0-rc01.txt b/preference/preference-ktx/api/res-1.1.0-rc01.txt
similarity index 100%
rename from preference/ktx/api/res-1.1.0-rc01.txt
rename to preference/preference-ktx/api/res-1.1.0-rc01.txt
diff --git a/preference/ktx/api/res-1.2.0-alpha01.txt b/preference/preference-ktx/api/res-1.2.0-alpha01.txt
similarity index 100%
rename from preference/ktx/api/res-1.2.0-alpha01.txt
rename to preference/preference-ktx/api/res-1.2.0-alpha01.txt
diff --git a/preference/ktx/api/restricted_1.0.0.txt b/preference/preference-ktx/api/restricted_1.0.0.txt
similarity index 100%
rename from preference/ktx/api/restricted_1.0.0.txt
rename to preference/preference-ktx/api/restricted_1.0.0.txt
diff --git a/preference/ktx/api/restricted_1.1.0-alpha04.txt b/preference/preference-ktx/api/restricted_1.1.0-alpha04.txt
similarity index 100%
rename from preference/ktx/api/restricted_1.1.0-alpha04.txt
rename to preference/preference-ktx/api/restricted_1.1.0-alpha04.txt
diff --git a/preference/ktx/api/restricted_1.1.0-alpha05.txt b/preference/preference-ktx/api/restricted_1.1.0-alpha05.txt
similarity index 100%
rename from preference/ktx/api/restricted_1.1.0-alpha05.txt
rename to preference/preference-ktx/api/restricted_1.1.0-alpha05.txt
diff --git a/preference/ktx/api/restricted_1.1.0-alpha06.txt b/preference/preference-ktx/api/restricted_1.1.0-alpha06.txt
similarity index 100%
rename from preference/ktx/api/restricted_1.1.0-alpha06.txt
rename to preference/preference-ktx/api/restricted_1.1.0-alpha06.txt
diff --git a/preference/ktx/api/restricted_1.1.0-beta01.txt b/preference/preference-ktx/api/restricted_1.1.0-beta01.txt
similarity index 100%
rename from preference/ktx/api/restricted_1.1.0-beta01.txt
rename to preference/preference-ktx/api/restricted_1.1.0-beta01.txt
diff --git a/preference/ktx/api/restricted_1.1.0-beta02.txt b/preference/preference-ktx/api/restricted_1.1.0-beta02.txt
similarity index 100%
rename from preference/ktx/api/restricted_1.1.0-beta02.txt
rename to preference/preference-ktx/api/restricted_1.1.0-beta02.txt
diff --git a/preference/ktx/api/restricted_1.1.0-rc01.txt b/preference/preference-ktx/api/restricted_1.1.0-rc01.txt
similarity index 100%
rename from preference/ktx/api/restricted_1.1.0-rc01.txt
rename to preference/preference-ktx/api/restricted_1.1.0-rc01.txt
diff --git a/preference/ktx/api/restricted_1.2.0-alpha01.txt b/preference/preference-ktx/api/restricted_1.2.0-alpha01.txt
similarity index 100%
rename from preference/ktx/api/restricted_1.2.0-alpha01.txt
rename to preference/preference-ktx/api/restricted_1.2.0-alpha01.txt
diff --git a/preference/ktx/api/restricted_current.txt b/preference/preference-ktx/api/restricted_current.txt
similarity index 100%
rename from preference/ktx/api/restricted_current.txt
rename to preference/preference-ktx/api/restricted_current.txt
diff --git a/preference/ktx/build.gradle b/preference/preference-ktx/build.gradle
similarity index 97%
rename from preference/ktx/build.gradle
rename to preference/preference-ktx/build.gradle
index 6852755..57fdce2 100644
--- a/preference/ktx/build.gradle
+++ b/preference/preference-ktx/build.gradle
@@ -40,7 +40,7 @@
}
dependencies {
- api(project(":preference"))
+ api(project(":preference:preference"))
api(KOTLIN_STDLIB)
androidTestImplementation(JUNIT)
diff --git a/preference/ktx/src/androidTest/AndroidManifest.xml b/preference/preference-ktx/src/androidTest/AndroidManifest.xml
similarity index 100%
rename from preference/ktx/src/androidTest/AndroidManifest.xml
rename to preference/preference-ktx/src/androidTest/AndroidManifest.xml
diff --git a/preference/ktx/src/androidTest/java/androidx/preference/PreferenceGroupTest.kt b/preference/preference-ktx/src/androidTest/java/androidx/preference/PreferenceGroupTest.kt
similarity index 100%
rename from preference/ktx/src/androidTest/java/androidx/preference/PreferenceGroupTest.kt
rename to preference/preference-ktx/src/androidTest/java/androidx/preference/PreferenceGroupTest.kt
diff --git a/preference/ktx/src/androidTest/java/androidx/preference/PreferenceTestHelperActivity.kt b/preference/preference-ktx/src/androidTest/java/androidx/preference/PreferenceTestHelperActivity.kt
similarity index 100%
rename from preference/ktx/src/androidTest/java/androidx/preference/PreferenceTestHelperActivity.kt
rename to preference/preference-ktx/src/androidTest/java/androidx/preference/PreferenceTestHelperActivity.kt
diff --git a/preference/ktx/src/androidTest/res/xml/test_preferencegroup.xml b/preference/preference-ktx/src/androidTest/res/xml/test_preferencegroup.xml
similarity index 100%
rename from preference/ktx/src/androidTest/res/xml/test_preferencegroup.xml
rename to preference/preference-ktx/src/androidTest/res/xml/test_preferencegroup.xml
diff --git a/preference/ktx/src/main/AndroidManifest.xml b/preference/preference-ktx/src/main/AndroidManifest.xml
similarity index 100%
rename from preference/ktx/src/main/AndroidManifest.xml
rename to preference/preference-ktx/src/main/AndroidManifest.xml
diff --git a/preference/ktx/src/main/java/androidx/preference/PreferenceGroup.kt b/preference/preference-ktx/src/main/java/androidx/preference/PreferenceGroup.kt
similarity index 100%
rename from preference/ktx/src/main/java/androidx/preference/PreferenceGroup.kt
rename to preference/preference-ktx/src/main/java/androidx/preference/PreferenceGroup.kt
diff --git a/preference/api/0.0.0.txt b/preference/preference/api/0.0.0.txt
similarity index 100%
rename from preference/api/0.0.0.txt
rename to preference/preference/api/0.0.0.txt
diff --git a/preference/api/1.0.0.txt b/preference/preference/api/1.0.0.txt
similarity index 100%
rename from preference/api/1.0.0.txt
rename to preference/preference/api/1.0.0.txt
diff --git a/preference/api/1.1.0-alpha01.txt b/preference/preference/api/1.1.0-alpha01.txt
similarity index 100%
rename from preference/api/1.1.0-alpha01.txt
rename to preference/preference/api/1.1.0-alpha01.txt
diff --git a/preference/api/1.1.0-alpha02.txt b/preference/preference/api/1.1.0-alpha02.txt
similarity index 100%
rename from preference/api/1.1.0-alpha02.txt
rename to preference/preference/api/1.1.0-alpha02.txt
diff --git a/preference/api/1.1.0-alpha03.txt b/preference/preference/api/1.1.0-alpha03.txt
similarity index 100%
rename from preference/api/1.1.0-alpha03.txt
rename to preference/preference/api/1.1.0-alpha03.txt
diff --git a/preference/api/1.1.0-alpha04.txt b/preference/preference/api/1.1.0-alpha04.txt
similarity index 100%
rename from preference/api/1.1.0-alpha04.txt
rename to preference/preference/api/1.1.0-alpha04.txt
diff --git a/preference/api/1.1.0-alpha05.txt b/preference/preference/api/1.1.0-alpha05.txt
similarity index 100%
rename from preference/api/1.1.0-alpha05.txt
rename to preference/preference/api/1.1.0-alpha05.txt
diff --git a/preference/api/1.1.0-alpha06.txt b/preference/preference/api/1.1.0-alpha06.txt
similarity index 100%
rename from preference/api/1.1.0-alpha06.txt
rename to preference/preference/api/1.1.0-alpha06.txt
diff --git a/preference/api/1.1.0-beta01.ignore b/preference/preference/api/1.1.0-beta01.ignore
similarity index 100%
rename from preference/api/1.1.0-beta01.ignore
rename to preference/preference/api/1.1.0-beta01.ignore
diff --git a/preference/api/1.1.0-beta01.txt b/preference/preference/api/1.1.0-beta01.txt
similarity index 100%
rename from preference/api/1.1.0-beta01.txt
rename to preference/preference/api/1.1.0-beta01.txt
diff --git a/preference/api/1.1.0-beta02.txt b/preference/preference/api/1.1.0-beta02.txt
similarity index 100%
rename from preference/api/1.1.0-beta02.txt
rename to preference/preference/api/1.1.0-beta02.txt
diff --git a/preference/api/1.1.0-rc01.txt b/preference/preference/api/1.1.0-rc01.txt
similarity index 100%
rename from preference/api/1.1.0-rc01.txt
rename to preference/preference/api/1.1.0-rc01.txt
diff --git a/preference/api/1.2.0-alpha01.txt b/preference/preference/api/1.2.0-alpha01.txt
similarity index 100%
rename from preference/api/1.2.0-alpha01.txt
rename to preference/preference/api/1.2.0-alpha01.txt
diff --git a/preference/api/api_lint.ignore b/preference/preference/api/api_lint.ignore
similarity index 100%
rename from preference/api/api_lint.ignore
rename to preference/preference/api/api_lint.ignore
diff --git a/preference/api/current.txt b/preference/preference/api/current.txt
similarity index 100%
rename from preference/api/current.txt
rename to preference/preference/api/current.txt
diff --git a/preference/api/res-1.1.0-alpha02.txt b/preference/preference/api/res-1.1.0-alpha02.txt
similarity index 100%
rename from preference/api/res-1.1.0-alpha02.txt
rename to preference/preference/api/res-1.1.0-alpha02.txt
diff --git a/preference/api/res-1.1.0-alpha03.txt b/preference/preference/api/res-1.1.0-alpha03.txt
similarity index 100%
rename from preference/api/res-1.1.0-alpha03.txt
rename to preference/preference/api/res-1.1.0-alpha03.txt
diff --git a/preference/api/res-1.1.0-alpha04.txt b/preference/preference/api/res-1.1.0-alpha04.txt
similarity index 100%
rename from preference/api/res-1.1.0-alpha04.txt
rename to preference/preference/api/res-1.1.0-alpha04.txt
diff --git a/preference/api/res-1.1.0-alpha05.txt b/preference/preference/api/res-1.1.0-alpha05.txt
similarity index 100%
rename from preference/api/res-1.1.0-alpha05.txt
rename to preference/preference/api/res-1.1.0-alpha05.txt
diff --git a/preference/api/res-1.1.0-alpha06.txt b/preference/preference/api/res-1.1.0-alpha06.txt
similarity index 100%
rename from preference/api/res-1.1.0-alpha06.txt
rename to preference/preference/api/res-1.1.0-alpha06.txt
diff --git a/preference/api/res-1.1.0-beta01.txt b/preference/preference/api/res-1.1.0-beta01.txt
similarity index 100%
rename from preference/api/res-1.1.0-beta01.txt
rename to preference/preference/api/res-1.1.0-beta01.txt
diff --git a/preference/api/res-1.1.0-beta02.txt b/preference/preference/api/res-1.1.0-beta02.txt
similarity index 100%
rename from preference/api/res-1.1.0-beta02.txt
rename to preference/preference/api/res-1.1.0-beta02.txt
diff --git a/preference/api/res-1.1.0-rc01.txt b/preference/preference/api/res-1.1.0-rc01.txt
similarity index 100%
rename from preference/api/res-1.1.0-rc01.txt
rename to preference/preference/api/res-1.1.0-rc01.txt
diff --git a/preference/api/res-1.2.0-alpha01.txt b/preference/preference/api/res-1.2.0-alpha01.txt
similarity index 100%
rename from preference/api/res-1.2.0-alpha01.txt
rename to preference/preference/api/res-1.2.0-alpha01.txt
diff --git a/preference/api/restricted_1.0.0.txt b/preference/preference/api/restricted_1.0.0.txt
similarity index 100%
rename from preference/api/restricted_1.0.0.txt
rename to preference/preference/api/restricted_1.0.0.txt
diff --git a/preference/api/restricted_1.1.0-alpha04.ignore b/preference/preference/api/restricted_1.1.0-alpha04.ignore
similarity index 100%
rename from preference/api/restricted_1.1.0-alpha04.ignore
rename to preference/preference/api/restricted_1.1.0-alpha04.ignore
diff --git a/preference/api/restricted_1.1.0-alpha04.txt b/preference/preference/api/restricted_1.1.0-alpha04.txt
similarity index 100%
rename from preference/api/restricted_1.1.0-alpha04.txt
rename to preference/preference/api/restricted_1.1.0-alpha04.txt
diff --git a/preference/api/restricted_1.1.0-alpha05.ignore b/preference/preference/api/restricted_1.1.0-alpha05.ignore
similarity index 100%
rename from preference/api/restricted_1.1.0-alpha05.ignore
rename to preference/preference/api/restricted_1.1.0-alpha05.ignore
diff --git a/preference/api/restricted_1.1.0-alpha05.txt b/preference/preference/api/restricted_1.1.0-alpha05.txt
similarity index 100%
rename from preference/api/restricted_1.1.0-alpha05.txt
rename to preference/preference/api/restricted_1.1.0-alpha05.txt
diff --git a/preference/api/restricted_1.1.0-alpha06.ignore b/preference/preference/api/restricted_1.1.0-alpha06.ignore
similarity index 100%
rename from preference/api/restricted_1.1.0-alpha06.ignore
rename to preference/preference/api/restricted_1.1.0-alpha06.ignore
diff --git a/preference/api/restricted_1.1.0-alpha06.txt b/preference/preference/api/restricted_1.1.0-alpha06.txt
similarity index 100%
rename from preference/api/restricted_1.1.0-alpha06.txt
rename to preference/preference/api/restricted_1.1.0-alpha06.txt
diff --git a/preference/api/restricted_1.1.0-beta01.ignore b/preference/preference/api/restricted_1.1.0-beta01.ignore
similarity index 100%
rename from preference/api/restricted_1.1.0-beta01.ignore
rename to preference/preference/api/restricted_1.1.0-beta01.ignore
diff --git a/preference/api/restricted_1.1.0-beta01.txt b/preference/preference/api/restricted_1.1.0-beta01.txt
similarity index 100%
rename from preference/api/restricted_1.1.0-beta01.txt
rename to preference/preference/api/restricted_1.1.0-beta01.txt
diff --git a/preference/api/restricted_1.1.0-beta02.txt b/preference/preference/api/restricted_1.1.0-beta02.txt
similarity index 100%
rename from preference/api/restricted_1.1.0-beta02.txt
rename to preference/preference/api/restricted_1.1.0-beta02.txt
diff --git a/preference/api/restricted_1.1.0-rc01.ignore b/preference/preference/api/restricted_1.1.0-rc01.ignore
similarity index 100%
rename from preference/api/restricted_1.1.0-rc01.ignore
rename to preference/preference/api/restricted_1.1.0-rc01.ignore
diff --git a/preference/api/restricted_1.1.0-rc01.txt b/preference/preference/api/restricted_1.1.0-rc01.txt
similarity index 100%
rename from preference/api/restricted_1.1.0-rc01.txt
rename to preference/preference/api/restricted_1.1.0-rc01.txt
diff --git a/preference/api/restricted_1.2.0-alpha01.txt b/preference/preference/api/restricted_1.2.0-alpha01.txt
similarity index 100%
rename from preference/api/restricted_1.2.0-alpha01.txt
rename to preference/preference/api/restricted_1.2.0-alpha01.txt
diff --git a/preference/api/restricted_current.txt b/preference/preference/api/restricted_current.txt
similarity index 100%
rename from preference/api/restricted_current.txt
rename to preference/preference/api/restricted_current.txt
diff --git a/preference/api_legacy/26.0.0.txt b/preference/preference/api_legacy/26.0.0.txt
similarity index 100%
rename from preference/api_legacy/26.0.0.txt
rename to preference/preference/api_legacy/26.0.0.txt
diff --git a/preference/api_legacy/26.1.0.txt b/preference/preference/api_legacy/26.1.0.txt
similarity index 100%
rename from preference/api_legacy/26.1.0.txt
rename to preference/preference/api_legacy/26.1.0.txt
diff --git a/preference/api_legacy/27.0.0.txt b/preference/preference/api_legacy/27.0.0.txt
similarity index 100%
rename from preference/api_legacy/27.0.0.txt
rename to preference/preference/api_legacy/27.0.0.txt
diff --git a/preference/api_legacy/27.1.0.txt b/preference/preference/api_legacy/27.1.0.txt
similarity index 100%
rename from preference/api_legacy/27.1.0.txt
rename to preference/preference/api_legacy/27.1.0.txt
diff --git a/preference/api_legacy/28.0.0-alpha1.txt b/preference/preference/api_legacy/28.0.0-alpha1.txt
similarity index 100%
rename from preference/api_legacy/28.0.0-alpha1.txt
rename to preference/preference/api_legacy/28.0.0-alpha1.txt
diff --git a/preference/api_legacy/current.txt b/preference/preference/api_legacy/current.txt
similarity index 100%
rename from preference/api_legacy/current.txt
rename to preference/preference/api_legacy/current.txt
diff --git a/preference/build.gradle b/preference/preference/build.gradle
similarity index 98%
rename from preference/build.gradle
rename to preference/preference/build.gradle
index 43b0f07..8235c7a 100644
--- a/preference/build.gradle
+++ b/preference/preference/build.gradle
@@ -30,7 +30,7 @@
implementation("androidx.annotation:annotation:1.1.0")
api("androidx.appcompat:appcompat:1.1.0-rc01")
// TODO: change to alpha05 after release
- api("androidx.core:core:1.1.0-rc01")
+ api("androidx.core:core:1.1.0")
implementation("androidx.collection:collection:1.0.0")
api("androidx.fragment:fragment:1.1.0-rc01")
api("androidx.recyclerview:recyclerview:1.0.0")
diff --git a/preference/lint-baseline.xml b/preference/preference/lint-baseline.xml
similarity index 100%
rename from preference/lint-baseline.xml
rename to preference/preference/lint-baseline.xml
diff --git a/preference/proguard-rules.pro b/preference/preference/proguard-rules.pro
similarity index 100%
rename from preference/proguard-rules.pro
rename to preference/preference/proguard-rules.pro
diff --git a/preference/res-public/values/public_attrs.xml b/preference/preference/res-public/values/public_attrs.xml
similarity index 100%
rename from preference/res-public/values/public_attrs.xml
rename to preference/preference/res-public/values/public_attrs.xml
diff --git a/preference/res-public/values/public_styles.xml b/preference/preference/res-public/values/public_styles.xml
similarity index 100%
rename from preference/res-public/values/public_styles.xml
rename to preference/preference/res-public/values/public_styles.xml
diff --git a/preference/res-public/values/public_themes.xml b/preference/preference/res-public/values/public_themes.xml
similarity index 100%
rename from preference/res-public/values/public_themes.xml
rename to preference/preference/res-public/values/public_themes.xml
diff --git a/preference/res/drawable-v21/ic_arrow_down_24dp.xml b/preference/preference/res/drawable-v21/ic_arrow_down_24dp.xml
similarity index 100%
rename from preference/res/drawable-v21/ic_arrow_down_24dp.xml
rename to preference/preference/res/drawable-v21/ic_arrow_down_24dp.xml
diff --git a/preference/res/drawable-v21/preference_list_divider_material.xml b/preference/preference/res/drawable-v21/preference_list_divider_material.xml
similarity index 100%
rename from preference/res/drawable-v21/preference_list_divider_material.xml
rename to preference/preference/res/drawable-v21/preference_list_divider_material.xml
diff --git a/preference/res/drawable/ic_arrow_down_24dp.xml b/preference/preference/res/drawable/ic_arrow_down_24dp.xml
similarity index 100%
rename from preference/res/drawable/ic_arrow_down_24dp.xml
rename to preference/preference/res/drawable/ic_arrow_down_24dp.xml
diff --git a/preference/res/drawable/preference_list_divider_material.xml b/preference/preference/res/drawable/preference_list_divider_material.xml
similarity index 100%
rename from preference/res/drawable/preference_list_divider_material.xml
rename to preference/preference/res/drawable/preference_list_divider_material.xml
diff --git a/preference/res/layout/expand_button.xml b/preference/preference/res/layout/expand_button.xml
similarity index 100%
rename from preference/res/layout/expand_button.xml
rename to preference/preference/res/layout/expand_button.xml
diff --git a/preference/res/layout/image_frame.xml b/preference/preference/res/layout/image_frame.xml
similarity index 100%
rename from preference/res/layout/image_frame.xml
rename to preference/preference/res/layout/image_frame.xml
diff --git a/preference/res/layout/preference.xml b/preference/preference/res/layout/preference.xml
similarity index 100%
rename from preference/res/layout/preference.xml
rename to preference/preference/res/layout/preference.xml
diff --git a/preference/res/layout/preference_category.xml b/preference/preference/res/layout/preference_category.xml
similarity index 100%
rename from preference/res/layout/preference_category.xml
rename to preference/preference/res/layout/preference_category.xml
diff --git a/preference/res/layout/preference_category_material.xml b/preference/preference/res/layout/preference_category_material.xml
similarity index 100%
rename from preference/res/layout/preference_category_material.xml
rename to preference/preference/res/layout/preference_category_material.xml
diff --git a/preference/res/layout/preference_dialog_edittext.xml b/preference/preference/res/layout/preference_dialog_edittext.xml
similarity index 100%
rename from preference/res/layout/preference_dialog_edittext.xml
rename to preference/preference/res/layout/preference_dialog_edittext.xml
diff --git a/preference/res/layout/preference_dropdown.xml b/preference/preference/res/layout/preference_dropdown.xml
similarity index 100%
rename from preference/res/layout/preference_dropdown.xml
rename to preference/preference/res/layout/preference_dropdown.xml
diff --git a/preference/res/layout/preference_dropdown_material.xml b/preference/preference/res/layout/preference_dropdown_material.xml
similarity index 100%
rename from preference/res/layout/preference_dropdown_material.xml
rename to preference/preference/res/layout/preference_dropdown_material.xml
diff --git a/preference/res/layout/preference_information.xml b/preference/preference/res/layout/preference_information.xml
similarity index 100%
rename from preference/res/layout/preference_information.xml
rename to preference/preference/res/layout/preference_information.xml
diff --git a/preference/res/layout/preference_information_material.xml b/preference/preference/res/layout/preference_information_material.xml
similarity index 100%
rename from preference/res/layout/preference_information_material.xml
rename to preference/preference/res/layout/preference_information_material.xml
diff --git a/preference/res/layout/preference_list_fragment.xml b/preference/preference/res/layout/preference_list_fragment.xml
similarity index 100%
rename from preference/res/layout/preference_list_fragment.xml
rename to preference/preference/res/layout/preference_list_fragment.xml
diff --git a/preference/res/layout/preference_material.xml b/preference/preference/res/layout/preference_material.xml
similarity index 100%
rename from preference/res/layout/preference_material.xml
rename to preference/preference/res/layout/preference_material.xml
diff --git a/preference/res/layout/preference_recyclerview.xml b/preference/preference/res/layout/preference_recyclerview.xml
similarity index 100%
rename from preference/res/layout/preference_recyclerview.xml
rename to preference/preference/res/layout/preference_recyclerview.xml
diff --git a/preference/res/layout/preference_widget_checkbox.xml b/preference/preference/res/layout/preference_widget_checkbox.xml
similarity index 100%
rename from preference/res/layout/preference_widget_checkbox.xml
rename to preference/preference/res/layout/preference_widget_checkbox.xml
diff --git a/preference/res/layout/preference_widget_seekbar.xml b/preference/preference/res/layout/preference_widget_seekbar.xml
similarity index 100%
rename from preference/res/layout/preference_widget_seekbar.xml
rename to preference/preference/res/layout/preference_widget_seekbar.xml
diff --git a/preference/res/layout/preference_widget_seekbar_material.xml b/preference/preference/res/layout/preference_widget_seekbar_material.xml
similarity index 100%
rename from preference/res/layout/preference_widget_seekbar_material.xml
rename to preference/preference/res/layout/preference_widget_seekbar_material.xml
diff --git a/preference/res/layout/preference_widget_switch.xml b/preference/preference/res/layout/preference_widget_switch.xml
similarity index 100%
rename from preference/res/layout/preference_widget_switch.xml
rename to preference/preference/res/layout/preference_widget_switch.xml
diff --git a/preference/res/layout/preference_widget_switch_compat.xml b/preference/preference/res/layout/preference_widget_switch_compat.xml
similarity index 100%
rename from preference/res/layout/preference_widget_switch_compat.xml
rename to preference/preference/res/layout/preference_widget_switch_compat.xml
diff --git a/preference/res/values-af/strings.xml b/preference/preference/res/values-af/strings.xml
similarity index 100%
rename from preference/res/values-af/strings.xml
rename to preference/preference/res/values-af/strings.xml
diff --git a/preference/res/values-am/strings.xml b/preference/preference/res/values-am/strings.xml
similarity index 100%
rename from preference/res/values-am/strings.xml
rename to preference/preference/res/values-am/strings.xml
diff --git a/preference/res/values-ar/strings.xml b/preference/preference/res/values-ar/strings.xml
similarity index 100%
rename from preference/res/values-ar/strings.xml
rename to preference/preference/res/values-ar/strings.xml
diff --git a/preference/res/values-as/strings.xml b/preference/preference/res/values-as/strings.xml
similarity index 100%
rename from preference/res/values-as/strings.xml
rename to preference/preference/res/values-as/strings.xml
diff --git a/preference/res/values-az/strings.xml b/preference/preference/res/values-az/strings.xml
similarity index 100%
rename from preference/res/values-az/strings.xml
rename to preference/preference/res/values-az/strings.xml
diff --git a/preference/res/values-b+sr+Latn/strings.xml b/preference/preference/res/values-b+sr+Latn/strings.xml
similarity index 100%
rename from preference/res/values-b+sr+Latn/strings.xml
rename to preference/preference/res/values-b+sr+Latn/strings.xml
diff --git a/preference/res/values-be/strings.xml b/preference/preference/res/values-be/strings.xml
similarity index 100%
rename from preference/res/values-be/strings.xml
rename to preference/preference/res/values-be/strings.xml
diff --git a/preference/res/values-bg/strings.xml b/preference/preference/res/values-bg/strings.xml
similarity index 100%
rename from preference/res/values-bg/strings.xml
rename to preference/preference/res/values-bg/strings.xml
diff --git a/preference/res/values-bn/strings.xml b/preference/preference/res/values-bn/strings.xml
similarity index 100%
rename from preference/res/values-bn/strings.xml
rename to preference/preference/res/values-bn/strings.xml
diff --git a/preference/res/values-bs/strings.xml b/preference/preference/res/values-bs/strings.xml
similarity index 100%
rename from preference/res/values-bs/strings.xml
rename to preference/preference/res/values-bs/strings.xml
diff --git a/preference/res/values-ca/strings.xml b/preference/preference/res/values-ca/strings.xml
similarity index 100%
rename from preference/res/values-ca/strings.xml
rename to preference/preference/res/values-ca/strings.xml
diff --git a/preference/res/values-cs/strings.xml b/preference/preference/res/values-cs/strings.xml
similarity index 100%
rename from preference/res/values-cs/strings.xml
rename to preference/preference/res/values-cs/strings.xml
diff --git a/preference/res/values-da/strings.xml b/preference/preference/res/values-da/strings.xml
similarity index 100%
rename from preference/res/values-da/strings.xml
rename to preference/preference/res/values-da/strings.xml
diff --git a/preference/res/values-de/strings.xml b/preference/preference/res/values-de/strings.xml
similarity index 100%
rename from preference/res/values-de/strings.xml
rename to preference/preference/res/values-de/strings.xml
diff --git a/preference/res/values-el/strings.xml b/preference/preference/res/values-el/strings.xml
similarity index 100%
rename from preference/res/values-el/strings.xml
rename to preference/preference/res/values-el/strings.xml
diff --git a/preference/res/values-en-rAU/strings.xml b/preference/preference/res/values-en-rAU/strings.xml
similarity index 100%
rename from preference/res/values-en-rAU/strings.xml
rename to preference/preference/res/values-en-rAU/strings.xml
diff --git a/preference/res/values-en-rCA/strings.xml b/preference/preference/res/values-en-rCA/strings.xml
similarity index 100%
rename from preference/res/values-en-rCA/strings.xml
rename to preference/preference/res/values-en-rCA/strings.xml
diff --git a/preference/res/values-en-rGB/strings.xml b/preference/preference/res/values-en-rGB/strings.xml
similarity index 100%
rename from preference/res/values-en-rGB/strings.xml
rename to preference/preference/res/values-en-rGB/strings.xml
diff --git a/preference/res/values-en-rIN/strings.xml b/preference/preference/res/values-en-rIN/strings.xml
similarity index 100%
rename from preference/res/values-en-rIN/strings.xml
rename to preference/preference/res/values-en-rIN/strings.xml
diff --git a/preference/res/values-en-rXC/strings.xml b/preference/preference/res/values-en-rXC/strings.xml
similarity index 100%
rename from preference/res/values-en-rXC/strings.xml
rename to preference/preference/res/values-en-rXC/strings.xml
diff --git a/preference/res/values-es-rUS/strings.xml b/preference/preference/res/values-es-rUS/strings.xml
similarity index 100%
rename from preference/res/values-es-rUS/strings.xml
rename to preference/preference/res/values-es-rUS/strings.xml
diff --git a/preference/res/values-es/strings.xml b/preference/preference/res/values-es/strings.xml
similarity index 100%
rename from preference/res/values-es/strings.xml
rename to preference/preference/res/values-es/strings.xml
diff --git a/preference/res/values-et/strings.xml b/preference/preference/res/values-et/strings.xml
similarity index 100%
rename from preference/res/values-et/strings.xml
rename to preference/preference/res/values-et/strings.xml
diff --git a/preference/res/values-eu/strings.xml b/preference/preference/res/values-eu/strings.xml
similarity index 100%
rename from preference/res/values-eu/strings.xml
rename to preference/preference/res/values-eu/strings.xml
diff --git a/preference/res/values-fa/strings.xml b/preference/preference/res/values-fa/strings.xml
similarity index 100%
rename from preference/res/values-fa/strings.xml
rename to preference/preference/res/values-fa/strings.xml
diff --git a/preference/res/values-fi/strings.xml b/preference/preference/res/values-fi/strings.xml
similarity index 100%
rename from preference/res/values-fi/strings.xml
rename to preference/preference/res/values-fi/strings.xml
diff --git a/preference/res/values-fr-rCA/strings.xml b/preference/preference/res/values-fr-rCA/strings.xml
similarity index 100%
rename from preference/res/values-fr-rCA/strings.xml
rename to preference/preference/res/values-fr-rCA/strings.xml
diff --git a/preference/res/values-fr/strings.xml b/preference/preference/res/values-fr/strings.xml
similarity index 100%
rename from preference/res/values-fr/strings.xml
rename to preference/preference/res/values-fr/strings.xml
diff --git a/preference/res/values-gl/strings.xml b/preference/preference/res/values-gl/strings.xml
similarity index 100%
rename from preference/res/values-gl/strings.xml
rename to preference/preference/res/values-gl/strings.xml
diff --git a/preference/res/values-gu/strings.xml b/preference/preference/res/values-gu/strings.xml
similarity index 100%
rename from preference/res/values-gu/strings.xml
rename to preference/preference/res/values-gu/strings.xml
diff --git a/preference/res/values-hi/strings.xml b/preference/preference/res/values-hi/strings.xml
similarity index 100%
rename from preference/res/values-hi/strings.xml
rename to preference/preference/res/values-hi/strings.xml
diff --git a/preference/res/values-hr/strings.xml b/preference/preference/res/values-hr/strings.xml
similarity index 100%
rename from preference/res/values-hr/strings.xml
rename to preference/preference/res/values-hr/strings.xml
diff --git a/preference/res/values-hu/strings.xml b/preference/preference/res/values-hu/strings.xml
similarity index 100%
rename from preference/res/values-hu/strings.xml
rename to preference/preference/res/values-hu/strings.xml
diff --git a/preference/res/values-hy/strings.xml b/preference/preference/res/values-hy/strings.xml
similarity index 100%
rename from preference/res/values-hy/strings.xml
rename to preference/preference/res/values-hy/strings.xml
diff --git a/preference/res/values-in/strings.xml b/preference/preference/res/values-in/strings.xml
similarity index 100%
rename from preference/res/values-in/strings.xml
rename to preference/preference/res/values-in/strings.xml
diff --git a/preference/res/values-is/strings.xml b/preference/preference/res/values-is/strings.xml
similarity index 100%
rename from preference/res/values-is/strings.xml
rename to preference/preference/res/values-is/strings.xml
diff --git a/preference/res/values-it/strings.xml b/preference/preference/res/values-it/strings.xml
similarity index 100%
rename from preference/res/values-it/strings.xml
rename to preference/preference/res/values-it/strings.xml
diff --git a/preference/res/values-iw/strings.xml b/preference/preference/res/values-iw/strings.xml
similarity index 100%
rename from preference/res/values-iw/strings.xml
rename to preference/preference/res/values-iw/strings.xml
diff --git a/preference/res/values-ja/strings.xml b/preference/preference/res/values-ja/strings.xml
similarity index 100%
rename from preference/res/values-ja/strings.xml
rename to preference/preference/res/values-ja/strings.xml
diff --git a/preference/res/values-ka/strings.xml b/preference/preference/res/values-ka/strings.xml
similarity index 100%
rename from preference/res/values-ka/strings.xml
rename to preference/preference/res/values-ka/strings.xml
diff --git a/preference/res/values-kk/strings.xml b/preference/preference/res/values-kk/strings.xml
similarity index 100%
rename from preference/res/values-kk/strings.xml
rename to preference/preference/res/values-kk/strings.xml
diff --git a/preference/res/values-km/strings.xml b/preference/preference/res/values-km/strings.xml
similarity index 100%
rename from preference/res/values-km/strings.xml
rename to preference/preference/res/values-km/strings.xml
diff --git a/preference/res/values-kn/strings.xml b/preference/preference/res/values-kn/strings.xml
similarity index 100%
rename from preference/res/values-kn/strings.xml
rename to preference/preference/res/values-kn/strings.xml
diff --git a/preference/res/values-ko/strings.xml b/preference/preference/res/values-ko/strings.xml
similarity index 100%
rename from preference/res/values-ko/strings.xml
rename to preference/preference/res/values-ko/strings.xml
diff --git a/preference/res/values-ky/strings.xml b/preference/preference/res/values-ky/strings.xml
similarity index 100%
rename from preference/res/values-ky/strings.xml
rename to preference/preference/res/values-ky/strings.xml
diff --git a/preference/res/values-lo/strings.xml b/preference/preference/res/values-lo/strings.xml
similarity index 100%
rename from preference/res/values-lo/strings.xml
rename to preference/preference/res/values-lo/strings.xml
diff --git a/preference/res/values-lt/strings.xml b/preference/preference/res/values-lt/strings.xml
similarity index 100%
rename from preference/res/values-lt/strings.xml
rename to preference/preference/res/values-lt/strings.xml
diff --git a/preference/res/values-lv/strings.xml b/preference/preference/res/values-lv/strings.xml
similarity index 100%
rename from preference/res/values-lv/strings.xml
rename to preference/preference/res/values-lv/strings.xml
diff --git a/preference/res/values-mk/strings.xml b/preference/preference/res/values-mk/strings.xml
similarity index 100%
rename from preference/res/values-mk/strings.xml
rename to preference/preference/res/values-mk/strings.xml
diff --git a/preference/res/values-ml/strings.xml b/preference/preference/res/values-ml/strings.xml
similarity index 100%
rename from preference/res/values-ml/strings.xml
rename to preference/preference/res/values-ml/strings.xml
diff --git a/preference/res/values-mn/strings.xml b/preference/preference/res/values-mn/strings.xml
similarity index 100%
rename from preference/res/values-mn/strings.xml
rename to preference/preference/res/values-mn/strings.xml
diff --git a/preference/res/values-mr/strings.xml b/preference/preference/res/values-mr/strings.xml
similarity index 100%
rename from preference/res/values-mr/strings.xml
rename to preference/preference/res/values-mr/strings.xml
diff --git a/preference/res/values-ms/strings.xml b/preference/preference/res/values-ms/strings.xml
similarity index 100%
rename from preference/res/values-ms/strings.xml
rename to preference/preference/res/values-ms/strings.xml
diff --git a/preference/res/values-my/strings.xml b/preference/preference/res/values-my/strings.xml
similarity index 100%
rename from preference/res/values-my/strings.xml
rename to preference/preference/res/values-my/strings.xml
diff --git a/preference/res/values-nb/strings.xml b/preference/preference/res/values-nb/strings.xml
similarity index 100%
rename from preference/res/values-nb/strings.xml
rename to preference/preference/res/values-nb/strings.xml
diff --git a/preference/res/values-ne/strings.xml b/preference/preference/res/values-ne/strings.xml
similarity index 100%
rename from preference/res/values-ne/strings.xml
rename to preference/preference/res/values-ne/strings.xml
diff --git a/preference/res/values-nl/strings.xml b/preference/preference/res/values-nl/strings.xml
similarity index 100%
rename from preference/res/values-nl/strings.xml
rename to preference/preference/res/values-nl/strings.xml
diff --git a/preference/res/values-or/strings.xml b/preference/preference/res/values-or/strings.xml
similarity index 100%
rename from preference/res/values-or/strings.xml
rename to preference/preference/res/values-or/strings.xml
diff --git a/preference/res/values-pa/strings.xml b/preference/preference/res/values-pa/strings.xml
similarity index 100%
rename from preference/res/values-pa/strings.xml
rename to preference/preference/res/values-pa/strings.xml
diff --git a/preference/res/values-pl/strings.xml b/preference/preference/res/values-pl/strings.xml
similarity index 100%
rename from preference/res/values-pl/strings.xml
rename to preference/preference/res/values-pl/strings.xml
diff --git a/preference/res/values-pt-rBR/strings.xml b/preference/preference/res/values-pt-rBR/strings.xml
similarity index 100%
rename from preference/res/values-pt-rBR/strings.xml
rename to preference/preference/res/values-pt-rBR/strings.xml
diff --git a/preference/res/values-pt-rPT/strings.xml b/preference/preference/res/values-pt-rPT/strings.xml
similarity index 100%
rename from preference/res/values-pt-rPT/strings.xml
rename to preference/preference/res/values-pt-rPT/strings.xml
diff --git a/preference/res/values-pt/strings.xml b/preference/preference/res/values-pt/strings.xml
similarity index 100%
rename from preference/res/values-pt/strings.xml
rename to preference/preference/res/values-pt/strings.xml
diff --git a/preference/res/values-ro/strings.xml b/preference/preference/res/values-ro/strings.xml
similarity index 100%
rename from preference/res/values-ro/strings.xml
rename to preference/preference/res/values-ro/strings.xml
diff --git a/preference/res/values-ru/strings.xml b/preference/preference/res/values-ru/strings.xml
similarity index 100%
rename from preference/res/values-ru/strings.xml
rename to preference/preference/res/values-ru/strings.xml
diff --git a/preference/res/values-si/strings.xml b/preference/preference/res/values-si/strings.xml
similarity index 100%
rename from preference/res/values-si/strings.xml
rename to preference/preference/res/values-si/strings.xml
diff --git a/preference/res/values-sk/strings.xml b/preference/preference/res/values-sk/strings.xml
similarity index 100%
rename from preference/res/values-sk/strings.xml
rename to preference/preference/res/values-sk/strings.xml
diff --git a/preference/res/values-sl/strings.xml b/preference/preference/res/values-sl/strings.xml
similarity index 100%
rename from preference/res/values-sl/strings.xml
rename to preference/preference/res/values-sl/strings.xml
diff --git a/preference/res/values-sq/strings.xml b/preference/preference/res/values-sq/strings.xml
similarity index 100%
rename from preference/res/values-sq/strings.xml
rename to preference/preference/res/values-sq/strings.xml
diff --git a/preference/res/values-sr/strings.xml b/preference/preference/res/values-sr/strings.xml
similarity index 100%
rename from preference/res/values-sr/strings.xml
rename to preference/preference/res/values-sr/strings.xml
diff --git a/preference/res/values-sv/strings.xml b/preference/preference/res/values-sv/strings.xml
similarity index 100%
rename from preference/res/values-sv/strings.xml
rename to preference/preference/res/values-sv/strings.xml
diff --git a/preference/res/values-sw/strings.xml b/preference/preference/res/values-sw/strings.xml
similarity index 100%
rename from preference/res/values-sw/strings.xml
rename to preference/preference/res/values-sw/strings.xml
diff --git a/preference/res/values-sw360dp/config.xml b/preference/preference/res/values-sw360dp/config.xml
similarity index 100%
rename from preference/res/values-sw360dp/config.xml
rename to preference/preference/res/values-sw360dp/config.xml
diff --git a/preference/res/values-ta/strings.xml b/preference/preference/res/values-ta/strings.xml
similarity index 100%
rename from preference/res/values-ta/strings.xml
rename to preference/preference/res/values-ta/strings.xml
diff --git a/preference/res/values-te/strings.xml b/preference/preference/res/values-te/strings.xml
similarity index 100%
rename from preference/res/values-te/strings.xml
rename to preference/preference/res/values-te/strings.xml
diff --git a/preference/res/values-th/strings.xml b/preference/preference/res/values-th/strings.xml
similarity index 100%
rename from preference/res/values-th/strings.xml
rename to preference/preference/res/values-th/strings.xml
diff --git a/preference/res/values-tl/strings.xml b/preference/preference/res/values-tl/strings.xml
similarity index 100%
rename from preference/res/values-tl/strings.xml
rename to preference/preference/res/values-tl/strings.xml
diff --git a/preference/res/values-tr/strings.xml b/preference/preference/res/values-tr/strings.xml
similarity index 100%
rename from preference/res/values-tr/strings.xml
rename to preference/preference/res/values-tr/strings.xml
diff --git a/preference/res/values-uk/strings.xml b/preference/preference/res/values-uk/strings.xml
similarity index 100%
rename from preference/res/values-uk/strings.xml
rename to preference/preference/res/values-uk/strings.xml
diff --git a/preference/res/values-ur/strings.xml b/preference/preference/res/values-ur/strings.xml
similarity index 100%
rename from preference/res/values-ur/strings.xml
rename to preference/preference/res/values-ur/strings.xml
diff --git a/preference/res/values-uz/strings.xml b/preference/preference/res/values-uz/strings.xml
similarity index 100%
rename from preference/res/values-uz/strings.xml
rename to preference/preference/res/values-uz/strings.xml
diff --git a/preference/res/values-v21/dimens.xml b/preference/preference/res/values-v21/dimens.xml
similarity index 100%
rename from preference/res/values-v21/dimens.xml
rename to preference/preference/res/values-v21/dimens.xml
diff --git a/preference/res/values-v21/styles.xml b/preference/preference/res/values-v21/styles.xml
similarity index 100%
rename from preference/res/values-v21/styles.xml
rename to preference/preference/res/values-v21/styles.xml
diff --git a/preference/res/values-vi/strings.xml b/preference/preference/res/values-vi/strings.xml
similarity index 100%
rename from preference/res/values-vi/strings.xml
rename to preference/preference/res/values-vi/strings.xml
diff --git a/preference/res/values-zh-rCN/strings.xml b/preference/preference/res/values-zh-rCN/strings.xml
similarity index 100%
rename from preference/res/values-zh-rCN/strings.xml
rename to preference/preference/res/values-zh-rCN/strings.xml
diff --git a/preference/res/values-zh-rHK/strings.xml b/preference/preference/res/values-zh-rHK/strings.xml
similarity index 100%
rename from preference/res/values-zh-rHK/strings.xml
rename to preference/preference/res/values-zh-rHK/strings.xml
diff --git a/preference/res/values-zh-rTW/strings.xml b/preference/preference/res/values-zh-rTW/strings.xml
similarity index 100%
rename from preference/res/values-zh-rTW/strings.xml
rename to preference/preference/res/values-zh-rTW/strings.xml
diff --git a/preference/res/values-zu/strings.xml b/preference/preference/res/values-zu/strings.xml
similarity index 100%
rename from preference/res/values-zu/strings.xml
rename to preference/preference/res/values-zu/strings.xml
diff --git a/preference/res/values/attrs.xml b/preference/preference/res/values/attrs.xml
similarity index 100%
rename from preference/res/values/attrs.xml
rename to preference/preference/res/values/attrs.xml
diff --git a/preference/res/values/colors.xml b/preference/preference/res/values/colors.xml
similarity index 100%
rename from preference/res/values/colors.xml
rename to preference/preference/res/values/colors.xml
diff --git a/preference/res/values/config.xml b/preference/preference/res/values/config.xml
similarity index 100%
rename from preference/res/values/config.xml
rename to preference/preference/res/values/config.xml
diff --git a/preference/res/values/dimens.xml b/preference/preference/res/values/dimens.xml
similarity index 100%
rename from preference/res/values/dimens.xml
rename to preference/preference/res/values/dimens.xml
diff --git a/preference/res/values/strings.xml b/preference/preference/res/values/strings.xml
similarity index 100%
rename from preference/res/values/strings.xml
rename to preference/preference/res/values/strings.xml
diff --git a/preference/res/values/styles.xml b/preference/preference/res/values/styles.xml
similarity index 100%
rename from preference/res/values/styles.xml
rename to preference/preference/res/values/styles.xml
diff --git a/preference/res/values/themes.xml b/preference/preference/res/values/themes.xml
similarity index 100%
rename from preference/res/values/themes.xml
rename to preference/preference/res/values/themes.xml
diff --git a/preference/src/androidTest/AndroidManifest.xml b/preference/preference/src/androidTest/AndroidManifest.xml
similarity index 100%
rename from preference/src/androidTest/AndroidManifest.xml
rename to preference/preference/src/androidTest/AndroidManifest.xml
diff --git a/preference/src/androidTest/java/androidx/preference/PreferenceGroupInitialExpandedChildrenCountTest.java b/preference/preference/src/androidTest/java/androidx/preference/PreferenceGroupInitialExpandedChildrenCountTest.java
similarity index 100%
rename from preference/src/androidTest/java/androidx/preference/PreferenceGroupInitialExpandedChildrenCountTest.java
rename to preference/preference/src/androidTest/java/androidx/preference/PreferenceGroupInitialExpandedChildrenCountTest.java
diff --git a/preference/src/androidTest/java/androidx/preference/tests/EditTextPreferenceTest.java b/preference/preference/src/androidTest/java/androidx/preference/tests/EditTextPreferenceTest.java
similarity index 100%
rename from preference/src/androidTest/java/androidx/preference/tests/EditTextPreferenceTest.java
rename to preference/preference/src/androidTest/java/androidx/preference/tests/EditTextPreferenceTest.java
diff --git a/preference/src/androidTest/java/androidx/preference/tests/ExpandablePreferenceTest.java b/preference/preference/src/androidTest/java/androidx/preference/tests/ExpandablePreferenceTest.java
similarity index 100%
rename from preference/src/androidTest/java/androidx/preference/tests/ExpandablePreferenceTest.java
rename to preference/preference/src/androidTest/java/androidx/preference/tests/ExpandablePreferenceTest.java
diff --git a/preference/src/androidTest/java/androidx/preference/tests/ListPreferenceSummaryTest.java b/preference/preference/src/androidTest/java/androidx/preference/tests/ListPreferenceSummaryTest.java
similarity index 100%
rename from preference/src/androidTest/java/androidx/preference/tests/ListPreferenceSummaryTest.java
rename to preference/preference/src/androidTest/java/androidx/preference/tests/ListPreferenceSummaryTest.java
diff --git a/preference/src/androidTest/java/androidx/preference/tests/PreferenceComparisonCallbackTest.java b/preference/preference/src/androidTest/java/androidx/preference/tests/PreferenceComparisonCallbackTest.java
similarity index 100%
rename from preference/src/androidTest/java/androidx/preference/tests/PreferenceComparisonCallbackTest.java
rename to preference/preference/src/androidTest/java/androidx/preference/tests/PreferenceComparisonCallbackTest.java
diff --git a/preference/src/androidTest/java/androidx/preference/tests/PreferenceCopyingTest.java b/preference/preference/src/androidTest/java/androidx/preference/tests/PreferenceCopyingTest.java
similarity index 100%
rename from preference/src/androidTest/java/androidx/preference/tests/PreferenceCopyingTest.java
rename to preference/preference/src/androidTest/java/androidx/preference/tests/PreferenceCopyingTest.java
diff --git a/preference/src/androidTest/java/androidx/preference/tests/PreferenceDataStoreTest.java b/preference/preference/src/androidTest/java/androidx/preference/tests/PreferenceDataStoreTest.java
similarity index 100%
rename from preference/src/androidTest/java/androidx/preference/tests/PreferenceDataStoreTest.java
rename to preference/preference/src/androidTest/java/androidx/preference/tests/PreferenceDataStoreTest.java
diff --git a/preference/src/androidTest/java/androidx/preference/tests/PreferenceGroupFindPreferenceTest.java b/preference/preference/src/androidTest/java/androidx/preference/tests/PreferenceGroupFindPreferenceTest.java
similarity index 100%
rename from preference/src/androidTest/java/androidx/preference/tests/PreferenceGroupFindPreferenceTest.java
rename to preference/preference/src/androidTest/java/androidx/preference/tests/PreferenceGroupFindPreferenceTest.java
diff --git a/preference/src/androidTest/java/androidx/preference/tests/PreferenceIconSpaceTest.java b/preference/preference/src/androidTest/java/androidx/preference/tests/PreferenceIconSpaceTest.java
similarity index 100%
rename from preference/src/androidTest/java/androidx/preference/tests/PreferenceIconSpaceTest.java
rename to preference/preference/src/androidTest/java/androidx/preference/tests/PreferenceIconSpaceTest.java
diff --git a/preference/src/androidTest/java/androidx/preference/tests/PreferenceParentGroupTest.java b/preference/preference/src/androidTest/java/androidx/preference/tests/PreferenceParentGroupTest.java
similarity index 100%
rename from preference/src/androidTest/java/androidx/preference/tests/PreferenceParentGroupTest.java
rename to preference/preference/src/androidTest/java/androidx/preference/tests/PreferenceParentGroupTest.java
diff --git a/preference/src/androidTest/java/androidx/preference/tests/PreferencePersistTest.java b/preference/preference/src/androidTest/java/androidx/preference/tests/PreferencePersistTest.java
similarity index 100%
rename from preference/src/androidTest/java/androidx/preference/tests/PreferencePersistTest.java
rename to preference/preference/src/androidTest/java/androidx/preference/tests/PreferencePersistTest.java
diff --git a/preference/src/androidTest/java/androidx/preference/tests/PreferenceSingleLineTitleTest.java b/preference/preference/src/androidTest/java/androidx/preference/tests/PreferenceSingleLineTitleTest.java
similarity index 100%
rename from preference/src/androidTest/java/androidx/preference/tests/PreferenceSingleLineTitleTest.java
rename to preference/preference/src/androidTest/java/androidx/preference/tests/PreferenceSingleLineTitleTest.java
diff --git a/preference/src/androidTest/java/androidx/preference/tests/PreferenceVisibilityTest.java b/preference/preference/src/androidTest/java/androidx/preference/tests/PreferenceVisibilityTest.java
similarity index 100%
rename from preference/src/androidTest/java/androidx/preference/tests/PreferenceVisibilityTest.java
rename to preference/preference/src/androidTest/java/androidx/preference/tests/PreferenceVisibilityTest.java
diff --git a/preference/src/androidTest/java/androidx/preference/tests/SeekBarPreferenceTest.kt b/preference/preference/src/androidTest/java/androidx/preference/tests/SeekBarPreferenceTest.kt
similarity index 100%
rename from preference/src/androidTest/java/androidx/preference/tests/SeekBarPreferenceTest.kt
rename to preference/preference/src/androidTest/java/androidx/preference/tests/SeekBarPreferenceTest.kt
diff --git a/preference/src/androidTest/java/androidx/preference/tests/SelectableTest.kt b/preference/preference/src/androidTest/java/androidx/preference/tests/SelectableTest.kt
similarity index 100%
rename from preference/src/androidTest/java/androidx/preference/tests/SelectableTest.kt
rename to preference/preference/src/androidTest/java/androidx/preference/tests/SelectableTest.kt
diff --git a/preference/src/androidTest/java/androidx/preference/tests/SummaryProviderTest.java b/preference/preference/src/androidTest/java/androidx/preference/tests/SummaryProviderTest.java
similarity index 100%
rename from preference/src/androidTest/java/androidx/preference/tests/SummaryProviderTest.java
rename to preference/preference/src/androidTest/java/androidx/preference/tests/SummaryProviderTest.java
diff --git a/preference/src/androidTest/java/androidx/preference/tests/helpers/PreferenceTestHelperActivity.java b/preference/preference/src/androidTest/java/androidx/preference/tests/helpers/PreferenceTestHelperActivity.java
similarity index 100%
rename from preference/src/androidTest/java/androidx/preference/tests/helpers/PreferenceTestHelperActivity.java
rename to preference/preference/src/androidTest/java/androidx/preference/tests/helpers/PreferenceTestHelperActivity.java
diff --git a/preference/src/androidTest/java/androidx/preference/tests/helpers/PreferenceWrapper.java b/preference/preference/src/androidTest/java/androidx/preference/tests/helpers/PreferenceWrapper.java
similarity index 100%
rename from preference/src/androidTest/java/androidx/preference/tests/helpers/PreferenceWrapper.java
rename to preference/preference/src/androidTest/java/androidx/preference/tests/helpers/PreferenceWrapper.java
diff --git a/preference/src/androidTest/res/values/styles.xml b/preference/preference/src/androidTest/res/values/styles.xml
similarity index 100%
rename from preference/src/androidTest/res/values/styles.xml
rename to preference/preference/src/androidTest/res/values/styles.xml
diff --git a/preference/src/androidTest/res/xml/test_copying.xml b/preference/preference/src/androidTest/res/xml/test_copying.xml
similarity index 100%
rename from preference/src/androidTest/res/xml/test_copying.xml
rename to preference/preference/src/androidTest/res/xml/test_copying.xml
diff --git a/preference/src/androidTest/res/xml/test_edit_text_preference.xml b/preference/preference/src/androidTest/res/xml/test_edit_text_preference.xml
similarity index 100%
rename from preference/src/androidTest/res/xml/test_edit_text_preference.xml
rename to preference/preference/src/androidTest/res/xml/test_edit_text_preference.xml
diff --git a/preference/src/androidTest/res/xml/test_seekbar.xml b/preference/preference/src/androidTest/res/xml/test_seekbar.xml
similarity index 100%
rename from preference/src/androidTest/res/xml/test_seekbar.xml
rename to preference/preference/src/androidTest/res/xml/test_seekbar.xml
diff --git a/preference/src/androidTest/res/xml/test_selectable.xml b/preference/preference/src/androidTest/res/xml/test_selectable.xml
similarity index 100%
rename from preference/src/androidTest/res/xml/test_selectable.xml
rename to preference/preference/src/androidTest/res/xml/test_selectable.xml
diff --git a/preference/src/androidTest/res/xml/test_visibility.xml b/preference/preference/src/androidTest/res/xml/test_visibility.xml
similarity index 100%
rename from preference/src/androidTest/res/xml/test_visibility.xml
rename to preference/preference/src/androidTest/res/xml/test_visibility.xml
diff --git a/preference/src/main/AndroidManifest.xml b/preference/preference/src/main/AndroidManifest.xml
similarity index 100%
rename from preference/src/main/AndroidManifest.xml
rename to preference/preference/src/main/AndroidManifest.xml
diff --git a/preference/src/main/java/androidx/preference/AndroidResources.java b/preference/preference/src/main/java/androidx/preference/AndroidResources.java
similarity index 100%
rename from preference/src/main/java/androidx/preference/AndroidResources.java
rename to preference/preference/src/main/java/androidx/preference/AndroidResources.java
diff --git a/preference/src/main/java/androidx/preference/CheckBoxPreference.java b/preference/preference/src/main/java/androidx/preference/CheckBoxPreference.java
similarity index 100%
rename from preference/src/main/java/androidx/preference/CheckBoxPreference.java
rename to preference/preference/src/main/java/androidx/preference/CheckBoxPreference.java
diff --git a/preference/src/main/java/androidx/preference/DialogPreference.java b/preference/preference/src/main/java/androidx/preference/DialogPreference.java
similarity index 100%
rename from preference/src/main/java/androidx/preference/DialogPreference.java
rename to preference/preference/src/main/java/androidx/preference/DialogPreference.java
diff --git a/preference/src/main/java/androidx/preference/DropDownPreference.java b/preference/preference/src/main/java/androidx/preference/DropDownPreference.java
similarity index 100%
rename from preference/src/main/java/androidx/preference/DropDownPreference.java
rename to preference/preference/src/main/java/androidx/preference/DropDownPreference.java
diff --git a/preference/src/main/java/androidx/preference/EditTextPreference.java b/preference/preference/src/main/java/androidx/preference/EditTextPreference.java
similarity index 100%
rename from preference/src/main/java/androidx/preference/EditTextPreference.java
rename to preference/preference/src/main/java/androidx/preference/EditTextPreference.java
diff --git a/preference/src/main/java/androidx/preference/EditTextPreferenceDialogFragment.java b/preference/preference/src/main/java/androidx/preference/EditTextPreferenceDialogFragment.java
similarity index 100%
rename from preference/src/main/java/androidx/preference/EditTextPreferenceDialogFragment.java
rename to preference/preference/src/main/java/androidx/preference/EditTextPreferenceDialogFragment.java
diff --git a/preference/src/main/java/androidx/preference/EditTextPreferenceDialogFragmentCompat.java b/preference/preference/src/main/java/androidx/preference/EditTextPreferenceDialogFragmentCompat.java
similarity index 100%
rename from preference/src/main/java/androidx/preference/EditTextPreferenceDialogFragmentCompat.java
rename to preference/preference/src/main/java/androidx/preference/EditTextPreferenceDialogFragmentCompat.java
diff --git a/preference/src/main/java/androidx/preference/ExpandButton.java b/preference/preference/src/main/java/androidx/preference/ExpandButton.java
similarity index 100%
rename from preference/src/main/java/androidx/preference/ExpandButton.java
rename to preference/preference/src/main/java/androidx/preference/ExpandButton.java
diff --git a/preference/src/main/java/androidx/preference/ListPreference.java b/preference/preference/src/main/java/androidx/preference/ListPreference.java
similarity index 100%
rename from preference/src/main/java/androidx/preference/ListPreference.java
rename to preference/preference/src/main/java/androidx/preference/ListPreference.java
diff --git a/preference/src/main/java/androidx/preference/ListPreferenceDialogFragment.java b/preference/preference/src/main/java/androidx/preference/ListPreferenceDialogFragment.java
similarity index 100%
rename from preference/src/main/java/androidx/preference/ListPreferenceDialogFragment.java
rename to preference/preference/src/main/java/androidx/preference/ListPreferenceDialogFragment.java
diff --git a/preference/src/main/java/androidx/preference/ListPreferenceDialogFragmentCompat.java b/preference/preference/src/main/java/androidx/preference/ListPreferenceDialogFragmentCompat.java
similarity index 100%
rename from preference/src/main/java/androidx/preference/ListPreferenceDialogFragmentCompat.java
rename to preference/preference/src/main/java/androidx/preference/ListPreferenceDialogFragmentCompat.java
diff --git a/preference/src/main/java/androidx/preference/MultiSelectListPreference.java b/preference/preference/src/main/java/androidx/preference/MultiSelectListPreference.java
similarity index 100%
rename from preference/src/main/java/androidx/preference/MultiSelectListPreference.java
rename to preference/preference/src/main/java/androidx/preference/MultiSelectListPreference.java
diff --git a/preference/src/main/java/androidx/preference/MultiSelectListPreferenceDialogFragment.java b/preference/preference/src/main/java/androidx/preference/MultiSelectListPreferenceDialogFragment.java
similarity index 100%
rename from preference/src/main/java/androidx/preference/MultiSelectListPreferenceDialogFragment.java
rename to preference/preference/src/main/java/androidx/preference/MultiSelectListPreferenceDialogFragment.java
diff --git a/preference/src/main/java/androidx/preference/MultiSelectListPreferenceDialogFragmentCompat.java b/preference/preference/src/main/java/androidx/preference/MultiSelectListPreferenceDialogFragmentCompat.java
similarity index 100%
rename from preference/src/main/java/androidx/preference/MultiSelectListPreferenceDialogFragmentCompat.java
rename to preference/preference/src/main/java/androidx/preference/MultiSelectListPreferenceDialogFragmentCompat.java
diff --git a/preference/src/main/java/androidx/preference/Preference.java b/preference/preference/src/main/java/androidx/preference/Preference.java
similarity index 100%
rename from preference/src/main/java/androidx/preference/Preference.java
rename to preference/preference/src/main/java/androidx/preference/Preference.java
diff --git a/preference/src/main/java/androidx/preference/PreferenceCategory.java b/preference/preference/src/main/java/androidx/preference/PreferenceCategory.java
similarity index 100%
rename from preference/src/main/java/androidx/preference/PreferenceCategory.java
rename to preference/preference/src/main/java/androidx/preference/PreferenceCategory.java
diff --git a/preference/src/main/java/androidx/preference/PreferenceDataStore.java b/preference/preference/src/main/java/androidx/preference/PreferenceDataStore.java
similarity index 100%
rename from preference/src/main/java/androidx/preference/PreferenceDataStore.java
rename to preference/preference/src/main/java/androidx/preference/PreferenceDataStore.java
diff --git a/preference/src/main/java/androidx/preference/PreferenceDialogFragment.java b/preference/preference/src/main/java/androidx/preference/PreferenceDialogFragment.java
similarity index 100%
rename from preference/src/main/java/androidx/preference/PreferenceDialogFragment.java
rename to preference/preference/src/main/java/androidx/preference/PreferenceDialogFragment.java
diff --git a/preference/src/main/java/androidx/preference/PreferenceDialogFragmentCompat.java b/preference/preference/src/main/java/androidx/preference/PreferenceDialogFragmentCompat.java
similarity index 100%
rename from preference/src/main/java/androidx/preference/PreferenceDialogFragmentCompat.java
rename to preference/preference/src/main/java/androidx/preference/PreferenceDialogFragmentCompat.java
diff --git a/preference/src/main/java/androidx/preference/PreferenceFragment.java b/preference/preference/src/main/java/androidx/preference/PreferenceFragment.java
similarity index 100%
rename from preference/src/main/java/androidx/preference/PreferenceFragment.java
rename to preference/preference/src/main/java/androidx/preference/PreferenceFragment.java
diff --git a/preference/src/main/java/androidx/preference/PreferenceFragmentCompat.java b/preference/preference/src/main/java/androidx/preference/PreferenceFragmentCompat.java
similarity index 100%
rename from preference/src/main/java/androidx/preference/PreferenceFragmentCompat.java
rename to preference/preference/src/main/java/androidx/preference/PreferenceFragmentCompat.java
diff --git a/preference/src/main/java/androidx/preference/PreferenceGroup.java b/preference/preference/src/main/java/androidx/preference/PreferenceGroup.java
similarity index 100%
rename from preference/src/main/java/androidx/preference/PreferenceGroup.java
rename to preference/preference/src/main/java/androidx/preference/PreferenceGroup.java
diff --git a/preference/src/main/java/androidx/preference/PreferenceGroupAdapter.java b/preference/preference/src/main/java/androidx/preference/PreferenceGroupAdapter.java
similarity index 100%
rename from preference/src/main/java/androidx/preference/PreferenceGroupAdapter.java
rename to preference/preference/src/main/java/androidx/preference/PreferenceGroupAdapter.java
diff --git a/preference/src/main/java/androidx/preference/PreferenceInflater.java b/preference/preference/src/main/java/androidx/preference/PreferenceInflater.java
similarity index 100%
rename from preference/src/main/java/androidx/preference/PreferenceInflater.java
rename to preference/preference/src/main/java/androidx/preference/PreferenceInflater.java
diff --git a/preference/src/main/java/androidx/preference/PreferenceManager.java b/preference/preference/src/main/java/androidx/preference/PreferenceManager.java
similarity index 100%
rename from preference/src/main/java/androidx/preference/PreferenceManager.java
rename to preference/preference/src/main/java/androidx/preference/PreferenceManager.java
diff --git a/preference/src/main/java/androidx/preference/PreferenceRecyclerViewAccessibilityDelegate.java b/preference/preference/src/main/java/androidx/preference/PreferenceRecyclerViewAccessibilityDelegate.java
similarity index 100%
rename from preference/src/main/java/androidx/preference/PreferenceRecyclerViewAccessibilityDelegate.java
rename to preference/preference/src/main/java/androidx/preference/PreferenceRecyclerViewAccessibilityDelegate.java
diff --git a/preference/src/main/java/androidx/preference/PreferenceScreen.java b/preference/preference/src/main/java/androidx/preference/PreferenceScreen.java
similarity index 100%
rename from preference/src/main/java/androidx/preference/PreferenceScreen.java
rename to preference/preference/src/main/java/androidx/preference/PreferenceScreen.java
diff --git a/preference/src/main/java/androidx/preference/PreferenceViewHolder.java b/preference/preference/src/main/java/androidx/preference/PreferenceViewHolder.java
similarity index 100%
rename from preference/src/main/java/androidx/preference/PreferenceViewHolder.java
rename to preference/preference/src/main/java/androidx/preference/PreferenceViewHolder.java
diff --git a/preference/src/main/java/androidx/preference/SeekBarPreference.java b/preference/preference/src/main/java/androidx/preference/SeekBarPreference.java
similarity index 100%
rename from preference/src/main/java/androidx/preference/SeekBarPreference.java
rename to preference/preference/src/main/java/androidx/preference/SeekBarPreference.java
diff --git a/preference/src/main/java/androidx/preference/SwitchPreference.java b/preference/preference/src/main/java/androidx/preference/SwitchPreference.java
similarity index 100%
rename from preference/src/main/java/androidx/preference/SwitchPreference.java
rename to preference/preference/src/main/java/androidx/preference/SwitchPreference.java
diff --git a/preference/src/main/java/androidx/preference/SwitchPreferenceCompat.java b/preference/preference/src/main/java/androidx/preference/SwitchPreferenceCompat.java
similarity index 100%
rename from preference/src/main/java/androidx/preference/SwitchPreferenceCompat.java
rename to preference/preference/src/main/java/androidx/preference/SwitchPreferenceCompat.java
diff --git a/preference/src/main/java/androidx/preference/TwoStatePreference.java b/preference/preference/src/main/java/androidx/preference/TwoStatePreference.java
similarity index 100%
rename from preference/src/main/java/androidx/preference/TwoStatePreference.java
rename to preference/preference/src/main/java/androidx/preference/TwoStatePreference.java
diff --git a/preference/src/main/java/androidx/preference/UnPressableLinearLayout.java b/preference/preference/src/main/java/androidx/preference/UnPressableLinearLayout.java
similarity index 100%
rename from preference/src/main/java/androidx/preference/UnPressableLinearLayout.java
rename to preference/preference/src/main/java/androidx/preference/UnPressableLinearLayout.java
diff --git a/preference/src/main/java/androidx/preference/internal/PreferenceImageView.java b/preference/preference/src/main/java/androidx/preference/internal/PreferenceImageView.java
similarity index 100%
rename from preference/src/main/java/androidx/preference/internal/PreferenceImageView.java
rename to preference/preference/src/main/java/androidx/preference/internal/PreferenceImageView.java
diff --git a/preference/src/main/java/androidx/preference/internal/package-info.java b/preference/preference/src/main/java/androidx/preference/internal/package-info.java
similarity index 100%
rename from preference/src/main/java/androidx/preference/internal/package-info.java
rename to preference/preference/src/main/java/androidx/preference/internal/package-info.java
diff --git a/preference/src/main/java/androidx/preference/package-info.java b/preference/preference/src/main/java/androidx/preference/package-info.java
similarity index 100%
rename from preference/src/main/java/androidx/preference/package-info.java
rename to preference/preference/src/main/java/androidx/preference/package-info.java
diff --git a/recyclerview/recyclerview-selection/build.gradle b/recyclerview/recyclerview-selection/build.gradle
index ac2f787..a364be0 100644
--- a/recyclerview/recyclerview-selection/build.gradle
+++ b/recyclerview/recyclerview-selection/build.gradle
@@ -27,7 +27,7 @@
dependencies {
api(project(":recyclerview:recyclerview"))
api("androidx.annotation:annotation:1.1.0")
- api("androidx.core:core:1.1.0-rc01")
+ api("androidx.core:core:1.1.0")
implementation("androidx.collection:collection:1.1.0")
androidTestImplementation(ANDROIDX_TEST_EXT_JUNIT)
diff --git a/recyclerview/recyclerview/api/1.1.0-beta04.txt b/recyclerview/recyclerview/api/1.1.0-beta04.txt
new file mode 100644
index 0000000..2e41ff1
--- /dev/null
+++ b/recyclerview/recyclerview/api/1.1.0-beta04.txt
@@ -0,0 +1,1037 @@
+// Signature format: 3.0
+package androidx.recyclerview.widget {
+
+ public final class AdapterListUpdateCallback implements androidx.recyclerview.widget.ListUpdateCallback {
+ ctor public AdapterListUpdateCallback(androidx.recyclerview.widget.RecyclerView.Adapter);
+ method public void onChanged(int, int, Object!);
+ method public void onInserted(int, int);
+ method public void onMoved(int, int);
+ method public void onRemoved(int, int);
+ }
+
+ public final class AsyncDifferConfig<T> {
+ method public java.util.concurrent.Executor getBackgroundThreadExecutor();
+ method public androidx.recyclerview.widget.DiffUtil.ItemCallback<T!> getDiffCallback();
+ }
+
+ public static final class AsyncDifferConfig.Builder<T> {
+ ctor public AsyncDifferConfig.Builder(androidx.recyclerview.widget.DiffUtil.ItemCallback<T!>);
+ method public androidx.recyclerview.widget.AsyncDifferConfig<T!> build();
+ method public androidx.recyclerview.widget.AsyncDifferConfig.Builder<T!> setBackgroundThreadExecutor(java.util.concurrent.Executor!);
+ }
+
+ public class AsyncListDiffer<T> {
+ ctor public AsyncListDiffer(androidx.recyclerview.widget.RecyclerView.Adapter, androidx.recyclerview.widget.DiffUtil.ItemCallback<T!>);
+ ctor public AsyncListDiffer(androidx.recyclerview.widget.ListUpdateCallback, androidx.recyclerview.widget.AsyncDifferConfig<T!>);
+ method public void addListListener(androidx.recyclerview.widget.AsyncListDiffer.ListListener<T!>);
+ method public java.util.List<T!> getCurrentList();
+ method public void removeListListener(androidx.recyclerview.widget.AsyncListDiffer.ListListener<T!>);
+ method public void submitList(java.util.List<T!>?);
+ method public void submitList(java.util.List<T!>?, Runnable?);
+ }
+
+ public static interface AsyncListDiffer.ListListener<T> {
+ method public void onCurrentListChanged(java.util.List<T!>, java.util.List<T!>);
+ }
+
+ public class AsyncListUtil<T> {
+ ctor public AsyncListUtil(Class<T!>, int, androidx.recyclerview.widget.AsyncListUtil.DataCallback<T!>, androidx.recyclerview.widget.AsyncListUtil.ViewCallback);
+ method public T? getItem(int);
+ method public int getItemCount();
+ method public void onRangeChanged();
+ method public void refresh();
+ }
+
+ public abstract static class AsyncListUtil.DataCallback<T> {
+ ctor public AsyncListUtil.DataCallback();
+ method @WorkerThread public abstract void fillData(T![], int, int);
+ method @WorkerThread public int getMaxCachedTiles();
+ method @WorkerThread public void recycleData(T![], int);
+ method @WorkerThread public abstract int refreshData();
+ }
+
+ public abstract static class AsyncListUtil.ViewCallback {
+ ctor public AsyncListUtil.ViewCallback();
+ method @UiThread public void extendRangeInto(int[], int[], int);
+ method @UiThread public abstract void getItemRangeInto(int[]);
+ method @UiThread public abstract void onDataRefresh();
+ method @UiThread public abstract void onItemLoaded(int);
+ field public static final int HINT_SCROLL_ASC = 2; // 0x2
+ field public static final int HINT_SCROLL_DESC = 1; // 0x1
+ field public static final int HINT_SCROLL_NONE = 0; // 0x0
+ }
+
+ public class BatchingListUpdateCallback implements androidx.recyclerview.widget.ListUpdateCallback {
+ ctor public BatchingListUpdateCallback(androidx.recyclerview.widget.ListUpdateCallback);
+ method public void dispatchLastEvent();
+ method public void onChanged(int, int, Object!);
+ method public void onInserted(int, int);
+ method public void onMoved(int, int);
+ method public void onRemoved(int, int);
+ }
+
+ public class DefaultItemAnimator extends androidx.recyclerview.widget.SimpleItemAnimator {
+ ctor public DefaultItemAnimator();
+ method public boolean animateAdd(androidx.recyclerview.widget.RecyclerView.ViewHolder!);
+ method public boolean animateChange(androidx.recyclerview.widget.RecyclerView.ViewHolder!, androidx.recyclerview.widget.RecyclerView.ViewHolder!, int, int, int, int);
+ method public boolean animateMove(androidx.recyclerview.widget.RecyclerView.ViewHolder!, int, int, int, int);
+ method public boolean animateRemove(androidx.recyclerview.widget.RecyclerView.ViewHolder!);
+ method public void endAnimation(androidx.recyclerview.widget.RecyclerView.ViewHolder!);
+ method public void endAnimations();
+ method public boolean isRunning();
+ method public void runPendingAnimations();
+ }
+
+ public class DiffUtil {
+ method public static androidx.recyclerview.widget.DiffUtil.DiffResult calculateDiff(androidx.recyclerview.widget.DiffUtil.Callback);
+ method public static androidx.recyclerview.widget.DiffUtil.DiffResult calculateDiff(androidx.recyclerview.widget.DiffUtil.Callback, boolean);
+ }
+
+ public abstract static class DiffUtil.Callback {
+ ctor public DiffUtil.Callback();
+ method public abstract boolean areContentsTheSame(int, int);
+ method public abstract boolean areItemsTheSame(int, int);
+ method public Object? getChangePayload(int, int);
+ method public abstract int getNewListSize();
+ method public abstract int getOldListSize();
+ }
+
+ public static class DiffUtil.DiffResult {
+ method public int convertNewPositionToOld(@IntRange(from=0) int);
+ method public int convertOldPositionToNew(@IntRange(from=0) int);
+ method public void dispatchUpdatesTo(androidx.recyclerview.widget.RecyclerView.Adapter);
+ method public void dispatchUpdatesTo(androidx.recyclerview.widget.ListUpdateCallback);
+ field public static final int NO_POSITION = -1; // 0xffffffff
+ }
+
+ public abstract static class DiffUtil.ItemCallback<T> {
+ ctor public DiffUtil.ItemCallback();
+ method public abstract boolean areContentsTheSame(T, T);
+ method public abstract boolean areItemsTheSame(T, T);
+ method public Object? getChangePayload(T, T);
+ }
+
+ public class DividerItemDecoration extends androidx.recyclerview.widget.RecyclerView.ItemDecoration {
+ ctor public DividerItemDecoration(android.content.Context!, int);
+ method public android.graphics.drawable.Drawable? getDrawable();
+ method public void setDrawable(android.graphics.drawable.Drawable);
+ method public void setOrientation(int);
+ field public static final int HORIZONTAL = 0; // 0x0
+ field public static final int VERTICAL = 1; // 0x1
+ }
+
+ public class GridLayoutManager extends androidx.recyclerview.widget.LinearLayoutManager {
+ ctor public GridLayoutManager(android.content.Context!, android.util.AttributeSet!, int, int);
+ ctor public GridLayoutManager(android.content.Context!, int);
+ ctor public GridLayoutManager(android.content.Context!, int, int, boolean);
+ method public int getSpanCount();
+ method public androidx.recyclerview.widget.GridLayoutManager.SpanSizeLookup! getSpanSizeLookup();
+ method public boolean isUsingSpansToEstimateScrollbarDimensions();
+ method public void setSpanCount(int);
+ method public void setSpanSizeLookup(androidx.recyclerview.widget.GridLayoutManager.SpanSizeLookup!);
+ method public void setUsingSpansToEstimateScrollbarDimensions(boolean);
+ field public static final int DEFAULT_SPAN_COUNT = -1; // 0xffffffff
+ }
+
+ public static final class GridLayoutManager.DefaultSpanSizeLookup extends androidx.recyclerview.widget.GridLayoutManager.SpanSizeLookup {
+ ctor public GridLayoutManager.DefaultSpanSizeLookup();
+ method public int getSpanSize(int);
+ }
+
+ public static class GridLayoutManager.LayoutParams extends androidx.recyclerview.widget.RecyclerView.LayoutParams {
+ ctor public GridLayoutManager.LayoutParams(android.content.Context!, android.util.AttributeSet!);
+ ctor public GridLayoutManager.LayoutParams(int, int);
+ ctor public GridLayoutManager.LayoutParams(android.view.ViewGroup.MarginLayoutParams!);
+ ctor public GridLayoutManager.LayoutParams(android.view.ViewGroup.LayoutParams!);
+ ctor public GridLayoutManager.LayoutParams(androidx.recyclerview.widget.RecyclerView.LayoutParams!);
+ method public int getSpanIndex();
+ method public int getSpanSize();
+ field public static final int INVALID_SPAN_ID = -1; // 0xffffffff
+ }
+
+ public abstract static class GridLayoutManager.SpanSizeLookup {
+ ctor public GridLayoutManager.SpanSizeLookup();
+ method public int getSpanGroupIndex(int, int);
+ method public int getSpanIndex(int, int);
+ method public abstract int getSpanSize(int);
+ method public void invalidateSpanGroupIndexCache();
+ method public void invalidateSpanIndexCache();
+ method public boolean isSpanGroupIndexCacheEnabled();
+ method public boolean isSpanIndexCacheEnabled();
+ method public void setSpanGroupIndexCacheEnabled(boolean);
+ method public void setSpanIndexCacheEnabled(boolean);
+ }
+
+ public class ItemTouchHelper extends androidx.recyclerview.widget.RecyclerView.ItemDecoration implements androidx.recyclerview.widget.RecyclerView.OnChildAttachStateChangeListener {
+ ctor public ItemTouchHelper(androidx.recyclerview.widget.ItemTouchHelper.Callback);
+ method public void attachToRecyclerView(androidx.recyclerview.widget.RecyclerView?);
+ method public void onChildViewAttachedToWindow(android.view.View);
+ method public void onChildViewDetachedFromWindow(android.view.View);
+ method public void startDrag(androidx.recyclerview.widget.RecyclerView.ViewHolder);
+ method public void startSwipe(androidx.recyclerview.widget.RecyclerView.ViewHolder);
+ field public static final int ACTION_STATE_DRAG = 2; // 0x2
+ field public static final int ACTION_STATE_IDLE = 0; // 0x0
+ field public static final int ACTION_STATE_SWIPE = 1; // 0x1
+ field public static final int ANIMATION_TYPE_DRAG = 8; // 0x8
+ field public static final int ANIMATION_TYPE_SWIPE_CANCEL = 4; // 0x4
+ field public static final int ANIMATION_TYPE_SWIPE_SUCCESS = 2; // 0x2
+ field public static final int DOWN = 2; // 0x2
+ field public static final int END = 32; // 0x20
+ field public static final int LEFT = 4; // 0x4
+ field public static final int RIGHT = 8; // 0x8
+ field public static final int START = 16; // 0x10
+ field public static final int UP = 1; // 0x1
+ }
+
+ public abstract static class ItemTouchHelper.Callback {
+ ctor public ItemTouchHelper.Callback();
+ method public boolean canDropOver(androidx.recyclerview.widget.RecyclerView, androidx.recyclerview.widget.RecyclerView.ViewHolder, androidx.recyclerview.widget.RecyclerView.ViewHolder);
+ method public androidx.recyclerview.widget.RecyclerView.ViewHolder! chooseDropTarget(androidx.recyclerview.widget.RecyclerView.ViewHolder, java.util.List<androidx.recyclerview.widget.RecyclerView.ViewHolder!>, int, int);
+ method public void clearView(androidx.recyclerview.widget.RecyclerView, androidx.recyclerview.widget.RecyclerView.ViewHolder);
+ method public int convertToAbsoluteDirection(int, int);
+ method public static int convertToRelativeDirection(int, int);
+ method public long getAnimationDuration(androidx.recyclerview.widget.RecyclerView, int, float, float);
+ method public int getBoundingBoxMargin();
+ method public static androidx.recyclerview.widget.ItemTouchUIUtil getDefaultUIUtil();
+ method public float getMoveThreshold(androidx.recyclerview.widget.RecyclerView.ViewHolder);
+ method public abstract int getMovementFlags(androidx.recyclerview.widget.RecyclerView, androidx.recyclerview.widget.RecyclerView.ViewHolder);
+ method public float getSwipeEscapeVelocity(float);
+ method public float getSwipeThreshold(androidx.recyclerview.widget.RecyclerView.ViewHolder);
+ method public float getSwipeVelocityThreshold(float);
+ method public int interpolateOutOfBoundsScroll(androidx.recyclerview.widget.RecyclerView, int, int, int, long);
+ method public boolean isItemViewSwipeEnabled();
+ method public boolean isLongPressDragEnabled();
+ method public static int makeFlag(int, int);
+ method public static int makeMovementFlags(int, int);
+ method public void onChildDraw(android.graphics.Canvas, androidx.recyclerview.widget.RecyclerView, androidx.recyclerview.widget.RecyclerView.ViewHolder, float, float, int, boolean);
+ method public void onChildDrawOver(android.graphics.Canvas, androidx.recyclerview.widget.RecyclerView, androidx.recyclerview.widget.RecyclerView.ViewHolder!, float, float, int, boolean);
+ method public abstract boolean onMove(androidx.recyclerview.widget.RecyclerView, androidx.recyclerview.widget.RecyclerView.ViewHolder, androidx.recyclerview.widget.RecyclerView.ViewHolder);
+ method public void onMoved(androidx.recyclerview.widget.RecyclerView, androidx.recyclerview.widget.RecyclerView.ViewHolder, int, androidx.recyclerview.widget.RecyclerView.ViewHolder, int, int, int);
+ method public void onSelectedChanged(androidx.recyclerview.widget.RecyclerView.ViewHolder?, int);
+ method public abstract void onSwiped(androidx.recyclerview.widget.RecyclerView.ViewHolder, int);
+ field public static final int DEFAULT_DRAG_ANIMATION_DURATION = 200; // 0xc8
+ field public static final int DEFAULT_SWIPE_ANIMATION_DURATION = 250; // 0xfa
+ }
+
+ public abstract static class ItemTouchHelper.SimpleCallback extends androidx.recyclerview.widget.ItemTouchHelper.Callback {
+ ctor public ItemTouchHelper.SimpleCallback(int, int);
+ method public int getDragDirs(androidx.recyclerview.widget.RecyclerView, androidx.recyclerview.widget.RecyclerView.ViewHolder);
+ method public int getMovementFlags(androidx.recyclerview.widget.RecyclerView, androidx.recyclerview.widget.RecyclerView.ViewHolder);
+ method public int getSwipeDirs(androidx.recyclerview.widget.RecyclerView, androidx.recyclerview.widget.RecyclerView.ViewHolder);
+ method public void setDefaultDragDirs(int);
+ method public void setDefaultSwipeDirs(int);
+ }
+
+ public static interface ItemTouchHelper.ViewDropHandler {
+ method public void prepareForDrop(android.view.View, android.view.View, int, int);
+ }
+
+ public interface ItemTouchUIUtil {
+ method public void clearView(android.view.View!);
+ method public void onDraw(android.graphics.Canvas!, androidx.recyclerview.widget.RecyclerView!, android.view.View!, float, float, int, boolean);
+ method public void onDrawOver(android.graphics.Canvas!, androidx.recyclerview.widget.RecyclerView!, android.view.View!, float, float, int, boolean);
+ method public void onSelected(android.view.View!);
+ }
+
+ public class LinearLayoutManager extends androidx.recyclerview.widget.RecyclerView.LayoutManager implements androidx.recyclerview.widget.ItemTouchHelper.ViewDropHandler androidx.recyclerview.widget.RecyclerView.SmoothScroller.ScrollVectorProvider {
+ ctor public LinearLayoutManager(android.content.Context!);
+ ctor public LinearLayoutManager(android.content.Context!, int, boolean);
+ ctor public LinearLayoutManager(android.content.Context!, android.util.AttributeSet!, int, int);
+ method protected void calculateExtraLayoutSpace(androidx.recyclerview.widget.RecyclerView.State, int[]);
+ method public android.graphics.PointF! computeScrollVectorForPosition(int);
+ method public int findFirstCompletelyVisibleItemPosition();
+ method public int findFirstVisibleItemPosition();
+ method public int findLastCompletelyVisibleItemPosition();
+ method public int findLastVisibleItemPosition();
+ method public androidx.recyclerview.widget.RecyclerView.LayoutParams! generateDefaultLayoutParams();
+ method @Deprecated protected int getExtraLayoutSpace(androidx.recyclerview.widget.RecyclerView.State!);
+ method public int getInitialPrefetchItemCount();
+ method public int getOrientation();
+ method public boolean getRecycleChildrenOnDetach();
+ method public boolean getReverseLayout();
+ method public boolean getStackFromEnd();
+ method protected boolean isLayoutRTL();
+ method public boolean isSmoothScrollbarEnabled();
+ method public void prepareForDrop(android.view.View, android.view.View, int, int);
+ method public void scrollToPositionWithOffset(int, int);
+ method public void setInitialPrefetchItemCount(int);
+ method public void setOrientation(int);
+ method public void setRecycleChildrenOnDetach(boolean);
+ method public void setReverseLayout(boolean);
+ method public void setSmoothScrollbarEnabled(boolean);
+ method public void setStackFromEnd(boolean);
+ field public static final int HORIZONTAL = 0; // 0x0
+ field public static final int INVALID_OFFSET = -2147483648; // 0x80000000
+ field public static final int VERTICAL = 1; // 0x1
+ }
+
+ protected static class LinearLayoutManager.LayoutChunkResult {
+ ctor protected LinearLayoutManager.LayoutChunkResult();
+ field public int mConsumed;
+ field public boolean mFinished;
+ field public boolean mFocusable;
+ field public boolean mIgnoreConsumed;
+ }
+
+ public class LinearSmoothScroller extends androidx.recyclerview.widget.RecyclerView.SmoothScroller {
+ ctor public LinearSmoothScroller(android.content.Context!);
+ method public int calculateDtToFit(int, int, int, int, int);
+ method public int calculateDxToMakeVisible(android.view.View!, int);
+ method public int calculateDyToMakeVisible(android.view.View!, int);
+ method protected float calculateSpeedPerPixel(android.util.DisplayMetrics!);
+ method protected int calculateTimeForDeceleration(int);
+ method protected int calculateTimeForScrolling(int);
+ method protected int getHorizontalSnapPreference();
+ method protected int getVerticalSnapPreference();
+ method protected void onSeekTargetStep(int, int, androidx.recyclerview.widget.RecyclerView.State!, androidx.recyclerview.widget.RecyclerView.SmoothScroller.Action!);
+ method protected void onStart();
+ method protected void onStop();
+ method protected void onTargetFound(android.view.View!, androidx.recyclerview.widget.RecyclerView.State!, androidx.recyclerview.widget.RecyclerView.SmoothScroller.Action!);
+ method protected void updateActionForInterimTarget(androidx.recyclerview.widget.RecyclerView.SmoothScroller.Action!);
+ field public static final int SNAP_TO_ANY = 0; // 0x0
+ field public static final int SNAP_TO_END = 1; // 0x1
+ field public static final int SNAP_TO_START = -1; // 0xffffffff
+ field protected final android.view.animation.DecelerateInterpolator! mDecelerateInterpolator;
+ field protected int mInterimTargetDx;
+ field protected int mInterimTargetDy;
+ field protected final android.view.animation.LinearInterpolator! mLinearInterpolator;
+ field protected android.graphics.PointF! mTargetVector;
+ }
+
+ public class LinearSnapHelper extends androidx.recyclerview.widget.SnapHelper {
+ ctor public LinearSnapHelper();
+ method public int[]! calculateDistanceToFinalSnap(androidx.recyclerview.widget.RecyclerView.LayoutManager, android.view.View);
+ method public android.view.View! findSnapView(androidx.recyclerview.widget.RecyclerView.LayoutManager!);
+ method public int findTargetSnapPosition(androidx.recyclerview.widget.RecyclerView.LayoutManager!, int, int);
+ }
+
+ public abstract class ListAdapter<T, VH extends androidx.recyclerview.widget.RecyclerView.ViewHolder> extends androidx.recyclerview.widget.RecyclerView.Adapter<VH> {
+ ctor protected ListAdapter(androidx.recyclerview.widget.DiffUtil.ItemCallback<T!>);
+ ctor protected ListAdapter(androidx.recyclerview.widget.AsyncDifferConfig<T!>);
+ method public java.util.List<T!> getCurrentList();
+ method protected T! getItem(int);
+ method public int getItemCount();
+ method public void onCurrentListChanged(java.util.List<T!>, java.util.List<T!>);
+ method public void submitList(java.util.List<T!>?);
+ method public void submitList(java.util.List<T!>?, Runnable?);
+ }
+
+ public interface ListUpdateCallback {
+ method public void onChanged(int, int, Object?);
+ method public void onInserted(int, int);
+ method public void onMoved(int, int);
+ method public void onRemoved(int, int);
+ }
+
+ public abstract class OrientationHelper {
+ method public static androidx.recyclerview.widget.OrientationHelper! createHorizontalHelper(androidx.recyclerview.widget.RecyclerView.LayoutManager!);
+ method public static androidx.recyclerview.widget.OrientationHelper! createOrientationHelper(androidx.recyclerview.widget.RecyclerView.LayoutManager!, int);
+ method public static androidx.recyclerview.widget.OrientationHelper! createVerticalHelper(androidx.recyclerview.widget.RecyclerView.LayoutManager!);
+ method public abstract int getDecoratedEnd(android.view.View!);
+ method public abstract int getDecoratedMeasurement(android.view.View!);
+ method public abstract int getDecoratedMeasurementInOther(android.view.View!);
+ method public abstract int getDecoratedStart(android.view.View!);
+ method public abstract int getEnd();
+ method public abstract int getEndAfterPadding();
+ method public abstract int getEndPadding();
+ method public androidx.recyclerview.widget.RecyclerView.LayoutManager! getLayoutManager();
+ method public abstract int getMode();
+ method public abstract int getModeInOther();
+ method public abstract int getStartAfterPadding();
+ method public abstract int getTotalSpace();
+ method public int getTotalSpaceChange();
+ method public abstract int getTransformedEndWithDecoration(android.view.View!);
+ method public abstract int getTransformedStartWithDecoration(android.view.View!);
+ method public abstract void offsetChild(android.view.View!, int);
+ method public abstract void offsetChildren(int);
+ method public void onLayoutComplete();
+ field public static final int HORIZONTAL = 0; // 0x0
+ field public static final int VERTICAL = 1; // 0x1
+ field protected final androidx.recyclerview.widget.RecyclerView.LayoutManager! mLayoutManager;
+ }
+
+ public class PagerSnapHelper extends androidx.recyclerview.widget.SnapHelper {
+ ctor public PagerSnapHelper();
+ method public int[]? calculateDistanceToFinalSnap(androidx.recyclerview.widget.RecyclerView.LayoutManager, android.view.View);
+ method protected androidx.recyclerview.widget.LinearSmoothScroller! createSnapScroller(androidx.recyclerview.widget.RecyclerView.LayoutManager!);
+ method public android.view.View? findSnapView(androidx.recyclerview.widget.RecyclerView.LayoutManager!);
+ method public int findTargetSnapPosition(androidx.recyclerview.widget.RecyclerView.LayoutManager!, int, int);
+ }
+
+ public class RecyclerView extends android.view.ViewGroup implements androidx.core.view.NestedScrollingChild2 androidx.core.view.NestedScrollingChild3 androidx.core.view.ScrollingView {
+ ctor public RecyclerView(android.content.Context);
+ ctor public RecyclerView(android.content.Context, android.util.AttributeSet?);
+ ctor public RecyclerView(android.content.Context, android.util.AttributeSet?, int);
+ method public void addItemDecoration(androidx.recyclerview.widget.RecyclerView.ItemDecoration, int);
+ method public void addItemDecoration(androidx.recyclerview.widget.RecyclerView.ItemDecoration);
+ method public void addOnChildAttachStateChangeListener(androidx.recyclerview.widget.RecyclerView.OnChildAttachStateChangeListener);
+ method public void addOnItemTouchListener(androidx.recyclerview.widget.RecyclerView.OnItemTouchListener);
+ method public void addOnScrollListener(androidx.recyclerview.widget.RecyclerView.OnScrollListener);
+ method public void clearOnChildAttachStateChangeListeners();
+ method public void clearOnScrollListeners();
+ method public int computeHorizontalScrollExtent();
+ method public int computeHorizontalScrollOffset();
+ method public int computeHorizontalScrollRange();
+ method public int computeVerticalScrollExtent();
+ method public int computeVerticalScrollOffset();
+ method public int computeVerticalScrollRange();
+ method public boolean dispatchNestedPreScroll(int, int, int[]!, int[]!, int);
+ method public boolean dispatchNestedScroll(int, int, int, int, int[]!, int);
+ method public final void dispatchNestedScroll(int, int, int, int, int[]!, int, int[]);
+ method public boolean drawChild(android.graphics.Canvas!, android.view.View!, long);
+ method public android.view.View? findChildViewUnder(float, float);
+ method public android.view.View? findContainingItemView(android.view.View);
+ method public androidx.recyclerview.widget.RecyclerView.ViewHolder? findContainingViewHolder(android.view.View);
+ method public androidx.recyclerview.widget.RecyclerView.ViewHolder? findViewHolderForAdapterPosition(int);
+ method public androidx.recyclerview.widget.RecyclerView.ViewHolder! findViewHolderForItemId(long);
+ method public androidx.recyclerview.widget.RecyclerView.ViewHolder? findViewHolderForLayoutPosition(int);
+ method @Deprecated public androidx.recyclerview.widget.RecyclerView.ViewHolder? findViewHolderForPosition(int);
+ method public boolean fling(int, int);
+ method public androidx.recyclerview.widget.RecyclerView.Adapter? getAdapter();
+ method public int getChildAdapterPosition(android.view.View);
+ method public long getChildItemId(android.view.View);
+ method public int getChildLayoutPosition(android.view.View);
+ method @Deprecated public int getChildPosition(android.view.View);
+ method public androidx.recyclerview.widget.RecyclerView.ViewHolder! getChildViewHolder(android.view.View);
+ method public androidx.recyclerview.widget.RecyclerViewAccessibilityDelegate? getCompatAccessibilityDelegate();
+ method public void getDecoratedBoundsWithMargins(android.view.View, android.graphics.Rect);
+ method public androidx.recyclerview.widget.RecyclerView.EdgeEffectFactory getEdgeEffectFactory();
+ method public androidx.recyclerview.widget.RecyclerView.ItemAnimator? getItemAnimator();
+ method public androidx.recyclerview.widget.RecyclerView.ItemDecoration getItemDecorationAt(int);
+ method public int getItemDecorationCount();
+ method public androidx.recyclerview.widget.RecyclerView.LayoutManager? getLayoutManager();
+ method public int getMaxFlingVelocity();
+ method public int getMinFlingVelocity();
+ method public androidx.recyclerview.widget.RecyclerView.OnFlingListener? getOnFlingListener();
+ method public boolean getPreserveFocusAfterLayout();
+ method public androidx.recyclerview.widget.RecyclerView.RecycledViewPool getRecycledViewPool();
+ method public int getScrollState();
+ method public boolean hasFixedSize();
+ method public boolean hasNestedScrollingParent(int);
+ method public boolean hasPendingAdapterUpdates();
+ method public void invalidateItemDecorations();
+ method public boolean isAnimating();
+ method public boolean isComputingLayout();
+ method @Deprecated public boolean isLayoutFrozen();
+ method public final boolean isLayoutSuppressed();
+ method public void offsetChildrenHorizontal(@Px int);
+ method public void offsetChildrenVertical(@Px int);
+ method public void onChildAttachedToWindow(android.view.View);
+ method public void onChildDetachedFromWindow(android.view.View);
+ method public void onDraw(android.graphics.Canvas!);
+ method public void onScrollStateChanged(int);
+ method public void onScrolled(@Px int, @Px int);
+ method public void removeItemDecoration(androidx.recyclerview.widget.RecyclerView.ItemDecoration);
+ method public void removeItemDecorationAt(int);
+ method public void removeOnChildAttachStateChangeListener(androidx.recyclerview.widget.RecyclerView.OnChildAttachStateChangeListener);
+ method public void removeOnItemTouchListener(androidx.recyclerview.widget.RecyclerView.OnItemTouchListener);
+ method public void removeOnScrollListener(androidx.recyclerview.widget.RecyclerView.OnScrollListener);
+ method public void scrollToPosition(int);
+ method public void setAccessibilityDelegateCompat(androidx.recyclerview.widget.RecyclerViewAccessibilityDelegate?);
+ method public void setAdapter(androidx.recyclerview.widget.RecyclerView.Adapter?);
+ method public void setChildDrawingOrderCallback(androidx.recyclerview.widget.RecyclerView.ChildDrawingOrderCallback?);
+ method public void setEdgeEffectFactory(androidx.recyclerview.widget.RecyclerView.EdgeEffectFactory);
+ method public void setHasFixedSize(boolean);
+ method public void setItemAnimator(androidx.recyclerview.widget.RecyclerView.ItemAnimator?);
+ method public void setItemViewCacheSize(int);
+ method @Deprecated public void setLayoutFrozen(boolean);
+ method public void setLayoutManager(androidx.recyclerview.widget.RecyclerView.LayoutManager?);
+ method @Deprecated public void setLayoutTransition(android.animation.LayoutTransition!);
+ method public void setOnFlingListener(androidx.recyclerview.widget.RecyclerView.OnFlingListener?);
+ method @Deprecated public void setOnScrollListener(androidx.recyclerview.widget.RecyclerView.OnScrollListener?);
+ method public void setPreserveFocusAfterLayout(boolean);
+ method public void setRecycledViewPool(androidx.recyclerview.widget.RecyclerView.RecycledViewPool?);
+ method public void setRecyclerListener(androidx.recyclerview.widget.RecyclerView.RecyclerListener?);
+ method public void setScrollingTouchSlop(int);
+ method public void setViewCacheExtension(androidx.recyclerview.widget.RecyclerView.ViewCacheExtension?);
+ method public void smoothScrollBy(@Px int, @Px int);
+ method public void smoothScrollBy(@Px int, @Px int, android.view.animation.Interpolator?);
+ method public void smoothScrollBy(@Px int, @Px int, android.view.animation.Interpolator?, int);
+ method public void smoothScrollToPosition(int);
+ method public boolean startNestedScroll(int, int);
+ method public void stopNestedScroll(int);
+ method public void stopScroll();
+ method public final void suppressLayout(boolean);
+ method public void swapAdapter(androidx.recyclerview.widget.RecyclerView.Adapter?, boolean);
+ field public static final int HORIZONTAL = 0; // 0x0
+ field public static final int INVALID_TYPE = -1; // 0xffffffff
+ field public static final long NO_ID = -1L; // 0xffffffffffffffffL
+ field public static final int NO_POSITION = -1; // 0xffffffff
+ field public static final int SCROLL_STATE_DRAGGING = 1; // 0x1
+ field public static final int SCROLL_STATE_IDLE = 0; // 0x0
+ field public static final int SCROLL_STATE_SETTLING = 2; // 0x2
+ field public static final int TOUCH_SLOP_DEFAULT = 0; // 0x0
+ field public static final int TOUCH_SLOP_PAGING = 1; // 0x1
+ field public static final int UNDEFINED_DURATION = -2147483648; // 0x80000000
+ field public static final int VERTICAL = 1; // 0x1
+ }
+
+ public abstract static class RecyclerView.Adapter<VH extends androidx.recyclerview.widget.RecyclerView.ViewHolder> {
+ ctor public RecyclerView.Adapter();
+ method public final void bindViewHolder(VH, int);
+ method public final VH createViewHolder(android.view.ViewGroup, int);
+ method public abstract int getItemCount();
+ method public long getItemId(int);
+ method public int getItemViewType(int);
+ method public final boolean hasObservers();
+ method public final boolean hasStableIds();
+ method public final void notifyDataSetChanged();
+ method public final void notifyItemChanged(int);
+ method public final void notifyItemChanged(int, Object?);
+ method public final void notifyItemInserted(int);
+ method public final void notifyItemMoved(int, int);
+ method public final void notifyItemRangeChanged(int, int);
+ method public final void notifyItemRangeChanged(int, int, Object?);
+ method public final void notifyItemRangeInserted(int, int);
+ method public final void notifyItemRangeRemoved(int, int);
+ method public final void notifyItemRemoved(int);
+ method public void onAttachedToRecyclerView(androidx.recyclerview.widget.RecyclerView);
+ method public abstract void onBindViewHolder(VH, int);
+ method public void onBindViewHolder(VH, int, java.util.List<java.lang.Object!>);
+ method public abstract VH onCreateViewHolder(android.view.ViewGroup, int);
+ method public void onDetachedFromRecyclerView(androidx.recyclerview.widget.RecyclerView);
+ method public boolean onFailedToRecycleView(VH);
+ method public void onViewAttachedToWindow(VH);
+ method public void onViewDetachedFromWindow(VH);
+ method public void onViewRecycled(VH);
+ method public void registerAdapterDataObserver(androidx.recyclerview.widget.RecyclerView.AdapterDataObserver);
+ method public void setHasStableIds(boolean);
+ method public void unregisterAdapterDataObserver(androidx.recyclerview.widget.RecyclerView.AdapterDataObserver);
+ }
+
+ public abstract static class RecyclerView.AdapterDataObserver {
+ ctor public RecyclerView.AdapterDataObserver();
+ method public void onChanged();
+ method public void onItemRangeChanged(int, int);
+ method public void onItemRangeChanged(int, int, Object?);
+ method public void onItemRangeInserted(int, int);
+ method public void onItemRangeMoved(int, int, int);
+ method public void onItemRangeRemoved(int, int);
+ }
+
+ public static interface RecyclerView.ChildDrawingOrderCallback {
+ method public int onGetChildDrawingOrder(int, int);
+ }
+
+ public static class RecyclerView.EdgeEffectFactory {
+ ctor public RecyclerView.EdgeEffectFactory();
+ method protected android.widget.EdgeEffect createEdgeEffect(androidx.recyclerview.widget.RecyclerView, @androidx.recyclerview.widget.RecyclerView.EdgeEffectFactory.EdgeDirection int);
+ field public static final int DIRECTION_BOTTOM = 3; // 0x3
+ field public static final int DIRECTION_LEFT = 0; // 0x0
+ field public static final int DIRECTION_RIGHT = 2; // 0x2
+ field public static final int DIRECTION_TOP = 1; // 0x1
+ }
+
+ @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE) @IntDef({androidx.recyclerview.widget.RecyclerView.EdgeEffectFactory.DIRECTION_LEFT, androidx.recyclerview.widget.RecyclerView.EdgeEffectFactory.DIRECTION_TOP, androidx.recyclerview.widget.RecyclerView.EdgeEffectFactory.DIRECTION_RIGHT, androidx.recyclerview.widget.RecyclerView.EdgeEffectFactory.DIRECTION_BOTTOM}) public static @interface RecyclerView.EdgeEffectFactory.EdgeDirection {
+ }
+
+ public abstract static class RecyclerView.ItemAnimator {
+ ctor public RecyclerView.ItemAnimator();
+ method public abstract boolean animateAppearance(androidx.recyclerview.widget.RecyclerView.ViewHolder, androidx.recyclerview.widget.RecyclerView.ItemAnimator.ItemHolderInfo?, androidx.recyclerview.widget.RecyclerView.ItemAnimator.ItemHolderInfo);
+ method public abstract boolean animateChange(androidx.recyclerview.widget.RecyclerView.ViewHolder, androidx.recyclerview.widget.RecyclerView.ViewHolder, androidx.recyclerview.widget.RecyclerView.ItemAnimator.ItemHolderInfo, androidx.recyclerview.widget.RecyclerView.ItemAnimator.ItemHolderInfo);
+ method public abstract boolean animateDisappearance(androidx.recyclerview.widget.RecyclerView.ViewHolder, androidx.recyclerview.widget.RecyclerView.ItemAnimator.ItemHolderInfo, androidx.recyclerview.widget.RecyclerView.ItemAnimator.ItemHolderInfo?);
+ method public abstract boolean animatePersistence(androidx.recyclerview.widget.RecyclerView.ViewHolder, androidx.recyclerview.widget.RecyclerView.ItemAnimator.ItemHolderInfo, androidx.recyclerview.widget.RecyclerView.ItemAnimator.ItemHolderInfo);
+ method public boolean canReuseUpdatedViewHolder(androidx.recyclerview.widget.RecyclerView.ViewHolder);
+ method public boolean canReuseUpdatedViewHolder(androidx.recyclerview.widget.RecyclerView.ViewHolder, java.util.List<java.lang.Object!>);
+ method public final void dispatchAnimationFinished(androidx.recyclerview.widget.RecyclerView.ViewHolder);
+ method public final void dispatchAnimationStarted(androidx.recyclerview.widget.RecyclerView.ViewHolder);
+ method public final void dispatchAnimationsFinished();
+ method public abstract void endAnimation(androidx.recyclerview.widget.RecyclerView.ViewHolder);
+ method public abstract void endAnimations();
+ method public long getAddDuration();
+ method public long getChangeDuration();
+ method public long getMoveDuration();
+ method public long getRemoveDuration();
+ method public abstract boolean isRunning();
+ method public final boolean isRunning(androidx.recyclerview.widget.RecyclerView.ItemAnimator.ItemAnimatorFinishedListener?);
+ method public androidx.recyclerview.widget.RecyclerView.ItemAnimator.ItemHolderInfo obtainHolderInfo();
+ method public void onAnimationFinished(androidx.recyclerview.widget.RecyclerView.ViewHolder);
+ method public void onAnimationStarted(androidx.recyclerview.widget.RecyclerView.ViewHolder);
+ method public androidx.recyclerview.widget.RecyclerView.ItemAnimator.ItemHolderInfo recordPostLayoutInformation(androidx.recyclerview.widget.RecyclerView.State, androidx.recyclerview.widget.RecyclerView.ViewHolder);
+ method public androidx.recyclerview.widget.RecyclerView.ItemAnimator.ItemHolderInfo recordPreLayoutInformation(androidx.recyclerview.widget.RecyclerView.State, androidx.recyclerview.widget.RecyclerView.ViewHolder, @androidx.recyclerview.widget.RecyclerView.ItemAnimator.AdapterChanges int, java.util.List<java.lang.Object!>);
+ method public abstract void runPendingAnimations();
+ method public void setAddDuration(long);
+ method public void setChangeDuration(long);
+ method public void setMoveDuration(long);
+ method public void setRemoveDuration(long);
+ field public static final int FLAG_APPEARED_IN_PRE_LAYOUT = 4096; // 0x1000
+ field public static final int FLAG_CHANGED = 2; // 0x2
+ field public static final int FLAG_INVALIDATED = 4; // 0x4
+ field public static final int FLAG_MOVED = 2048; // 0x800
+ field public static final int FLAG_REMOVED = 8; // 0x8
+ }
+
+ @IntDef(flag=true, value={androidx.recyclerview.widget.RecyclerView.ItemAnimator.FLAG_CHANGED, androidx.recyclerview.widget.RecyclerView.ItemAnimator.FLAG_REMOVED, androidx.recyclerview.widget.RecyclerView.ItemAnimator.FLAG_MOVED, androidx.recyclerview.widget.RecyclerView.ItemAnimator.FLAG_INVALIDATED, androidx.recyclerview.widget.RecyclerView.ItemAnimator.FLAG_APPEARED_IN_PRE_LAYOUT}) @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE) public static @interface RecyclerView.ItemAnimator.AdapterChanges {
+ }
+
+ public static interface RecyclerView.ItemAnimator.ItemAnimatorFinishedListener {
+ method public void onAnimationsFinished();
+ }
+
+ public static class RecyclerView.ItemAnimator.ItemHolderInfo {
+ ctor public RecyclerView.ItemAnimator.ItemHolderInfo();
+ method public androidx.recyclerview.widget.RecyclerView.ItemAnimator.ItemHolderInfo setFrom(androidx.recyclerview.widget.RecyclerView.ViewHolder);
+ method public androidx.recyclerview.widget.RecyclerView.ItemAnimator.ItemHolderInfo setFrom(androidx.recyclerview.widget.RecyclerView.ViewHolder, @androidx.recyclerview.widget.RecyclerView.ItemAnimator.AdapterChanges int);
+ field public int bottom;
+ field @androidx.recyclerview.widget.RecyclerView.ItemAnimator.AdapterChanges public int changeFlags;
+ field public int left;
+ field public int right;
+ field public int top;
+ }
+
+ public abstract static class RecyclerView.ItemDecoration {
+ ctor public RecyclerView.ItemDecoration();
+ method @Deprecated public void getItemOffsets(android.graphics.Rect, int, androidx.recyclerview.widget.RecyclerView);
+ method public void getItemOffsets(android.graphics.Rect, android.view.View, androidx.recyclerview.widget.RecyclerView, androidx.recyclerview.widget.RecyclerView.State);
+ method public void onDraw(android.graphics.Canvas, androidx.recyclerview.widget.RecyclerView, androidx.recyclerview.widget.RecyclerView.State);
+ method @Deprecated public void onDraw(android.graphics.Canvas, androidx.recyclerview.widget.RecyclerView);
+ method public void onDrawOver(android.graphics.Canvas, androidx.recyclerview.widget.RecyclerView, androidx.recyclerview.widget.RecyclerView.State);
+ method @Deprecated public void onDrawOver(android.graphics.Canvas, androidx.recyclerview.widget.RecyclerView);
+ }
+
+ public abstract static class RecyclerView.LayoutManager {
+ ctor public RecyclerView.LayoutManager();
+ method public void addDisappearingView(android.view.View!);
+ method public void addDisappearingView(android.view.View!, int);
+ method public void addView(android.view.View!);
+ method public void addView(android.view.View!, int);
+ method public void assertInLayoutOrScroll(String!);
+ method public void assertNotInLayoutOrScroll(String!);
+ method public void attachView(android.view.View, int, androidx.recyclerview.widget.RecyclerView.LayoutParams!);
+ method public void attachView(android.view.View, int);
+ method public void attachView(android.view.View);
+ method public void calculateItemDecorationsForChild(android.view.View, android.graphics.Rect);
+ method public boolean canScrollHorizontally();
+ method public boolean canScrollVertically();
+ method public boolean checkLayoutParams(androidx.recyclerview.widget.RecyclerView.LayoutParams!);
+ method public static int chooseSize(int, int, int);
+ method public void collectAdjacentPrefetchPositions(int, int, androidx.recyclerview.widget.RecyclerView.State!, androidx.recyclerview.widget.RecyclerView.LayoutManager.LayoutPrefetchRegistry!);
+ method public void collectInitialPrefetchPositions(int, androidx.recyclerview.widget.RecyclerView.LayoutManager.LayoutPrefetchRegistry!);
+ method public int computeHorizontalScrollExtent(androidx.recyclerview.widget.RecyclerView.State);
+ method public int computeHorizontalScrollOffset(androidx.recyclerview.widget.RecyclerView.State);
+ method public int computeHorizontalScrollRange(androidx.recyclerview.widget.RecyclerView.State);
+ method public int computeVerticalScrollExtent(androidx.recyclerview.widget.RecyclerView.State);
+ method public int computeVerticalScrollOffset(androidx.recyclerview.widget.RecyclerView.State);
+ method public int computeVerticalScrollRange(androidx.recyclerview.widget.RecyclerView.State);
+ method public void detachAndScrapAttachedViews(androidx.recyclerview.widget.RecyclerView.Recycler);
+ method public void detachAndScrapView(android.view.View, androidx.recyclerview.widget.RecyclerView.Recycler);
+ method public void detachAndScrapViewAt(int, androidx.recyclerview.widget.RecyclerView.Recycler);
+ method public void detachView(android.view.View);
+ method public void detachViewAt(int);
+ method public void endAnimation(android.view.View!);
+ method public android.view.View? findContainingItemView(android.view.View);
+ method public android.view.View? findViewByPosition(int);
+ method public abstract androidx.recyclerview.widget.RecyclerView.LayoutParams! generateDefaultLayoutParams();
+ method public androidx.recyclerview.widget.RecyclerView.LayoutParams! generateLayoutParams(android.view.ViewGroup.LayoutParams!);
+ method public androidx.recyclerview.widget.RecyclerView.LayoutParams! generateLayoutParams(android.content.Context!, android.util.AttributeSet!);
+ method public int getBaseline();
+ method public int getBottomDecorationHeight(android.view.View);
+ method public android.view.View? getChildAt(int);
+ method public int getChildCount();
+ method @Deprecated public static int getChildMeasureSpec(int, int, int, boolean);
+ method public static int getChildMeasureSpec(int, int, int, int, boolean);
+ method public boolean getClipToPadding();
+ method public int getColumnCountForAccessibility(androidx.recyclerview.widget.RecyclerView.Recycler, androidx.recyclerview.widget.RecyclerView.State);
+ method public int getDecoratedBottom(android.view.View);
+ method public void getDecoratedBoundsWithMargins(android.view.View, android.graphics.Rect);
+ method public int getDecoratedLeft(android.view.View);
+ method public int getDecoratedMeasuredHeight(android.view.View);
+ method public int getDecoratedMeasuredWidth(android.view.View);
+ method public int getDecoratedRight(android.view.View);
+ method public int getDecoratedTop(android.view.View);
+ method public android.view.View? getFocusedChild();
+ method @Px public int getHeight();
+ method public int getHeightMode();
+ method public int getItemCount();
+ method public int getItemViewType(android.view.View);
+ method public int getLayoutDirection();
+ method public int getLeftDecorationWidth(android.view.View);
+ method @Px public int getMinimumHeight();
+ method @Px public int getMinimumWidth();
+ method @Px public int getPaddingBottom();
+ method @Px public int getPaddingEnd();
+ method @Px public int getPaddingLeft();
+ method @Px public int getPaddingRight();
+ method @Px public int getPaddingStart();
+ method @Px public int getPaddingTop();
+ method public int getPosition(android.view.View);
+ method public static androidx.recyclerview.widget.RecyclerView.LayoutManager.Properties! getProperties(android.content.Context, android.util.AttributeSet?, int, int);
+ method public int getRightDecorationWidth(android.view.View);
+ method public int getRowCountForAccessibility(androidx.recyclerview.widget.RecyclerView.Recycler, androidx.recyclerview.widget.RecyclerView.State);
+ method public int getSelectionModeForAccessibility(androidx.recyclerview.widget.RecyclerView.Recycler, androidx.recyclerview.widget.RecyclerView.State);
+ method public int getTopDecorationHeight(android.view.View);
+ method public void getTransformedBoundingBox(android.view.View, boolean, android.graphics.Rect);
+ method @Px public int getWidth();
+ method public int getWidthMode();
+ method public boolean hasFocus();
+ method public void ignoreView(android.view.View);
+ method public boolean isAttachedToWindow();
+ method public boolean isAutoMeasureEnabled();
+ method public boolean isFocused();
+ method public final boolean isItemPrefetchEnabled();
+ method public boolean isLayoutHierarchical(androidx.recyclerview.widget.RecyclerView.Recycler, androidx.recyclerview.widget.RecyclerView.State);
+ method public boolean isMeasurementCacheEnabled();
+ method public boolean isSmoothScrolling();
+ method public boolean isViewPartiallyVisible(android.view.View, boolean, boolean);
+ method public void layoutDecorated(android.view.View, int, int, int, int);
+ method public void layoutDecoratedWithMargins(android.view.View, int, int, int, int);
+ method public void measureChild(android.view.View, int, int);
+ method public void measureChildWithMargins(android.view.View, int, int);
+ method public void moveView(int, int);
+ method public void offsetChildrenHorizontal(@Px int);
+ method public void offsetChildrenVertical(@Px int);
+ method public void onAdapterChanged(androidx.recyclerview.widget.RecyclerView.Adapter?, androidx.recyclerview.widget.RecyclerView.Adapter?);
+ method public boolean onAddFocusables(androidx.recyclerview.widget.RecyclerView, java.util.ArrayList<android.view.View!>, int, int);
+ method @CallSuper public void onAttachedToWindow(androidx.recyclerview.widget.RecyclerView!);
+ method @Deprecated public void onDetachedFromWindow(androidx.recyclerview.widget.RecyclerView!);
+ method @CallSuper public void onDetachedFromWindow(androidx.recyclerview.widget.RecyclerView!, androidx.recyclerview.widget.RecyclerView.Recycler!);
+ method public android.view.View? onFocusSearchFailed(android.view.View, int, androidx.recyclerview.widget.RecyclerView.Recycler, androidx.recyclerview.widget.RecyclerView.State);
+ method public void onInitializeAccessibilityEvent(android.view.accessibility.AccessibilityEvent);
+ method public void onInitializeAccessibilityEvent(androidx.recyclerview.widget.RecyclerView.Recycler, androidx.recyclerview.widget.RecyclerView.State, android.view.accessibility.AccessibilityEvent);
+ method public void onInitializeAccessibilityNodeInfo(androidx.recyclerview.widget.RecyclerView.Recycler, androidx.recyclerview.widget.RecyclerView.State, androidx.core.view.accessibility.AccessibilityNodeInfoCompat);
+ method public void onInitializeAccessibilityNodeInfoForItem(androidx.recyclerview.widget.RecyclerView.Recycler, androidx.recyclerview.widget.RecyclerView.State, android.view.View, androidx.core.view.accessibility.AccessibilityNodeInfoCompat);
+ method public android.view.View? onInterceptFocusSearch(android.view.View, int);
+ method public void onItemsAdded(androidx.recyclerview.widget.RecyclerView, int, int);
+ method public void onItemsChanged(androidx.recyclerview.widget.RecyclerView);
+ method public void onItemsMoved(androidx.recyclerview.widget.RecyclerView, int, int, int);
+ method public void onItemsRemoved(androidx.recyclerview.widget.RecyclerView, int, int);
+ method public void onItemsUpdated(androidx.recyclerview.widget.RecyclerView, int, int);
+ method public void onItemsUpdated(androidx.recyclerview.widget.RecyclerView, int, int, Object?);
+ method public void onLayoutChildren(androidx.recyclerview.widget.RecyclerView.Recycler!, androidx.recyclerview.widget.RecyclerView.State!);
+ method public void onLayoutCompleted(androidx.recyclerview.widget.RecyclerView.State!);
+ method public void onMeasure(androidx.recyclerview.widget.RecyclerView.Recycler, androidx.recyclerview.widget.RecyclerView.State, int, int);
+ method @Deprecated public boolean onRequestChildFocus(androidx.recyclerview.widget.RecyclerView, android.view.View, android.view.View?);
+ method public boolean onRequestChildFocus(androidx.recyclerview.widget.RecyclerView, androidx.recyclerview.widget.RecyclerView.State, android.view.View, android.view.View?);
+ method public void onRestoreInstanceState(android.os.Parcelable!);
+ method public android.os.Parcelable? onSaveInstanceState();
+ method public void onScrollStateChanged(int);
+ method public boolean performAccessibilityAction(androidx.recyclerview.widget.RecyclerView.Recycler, androidx.recyclerview.widget.RecyclerView.State, int, android.os.Bundle?);
+ method public boolean performAccessibilityActionForItem(androidx.recyclerview.widget.RecyclerView.Recycler, androidx.recyclerview.widget.RecyclerView.State, android.view.View, int, android.os.Bundle?);
+ method public void postOnAnimation(Runnable!);
+ method public void removeAllViews();
+ method public void removeAndRecycleAllViews(androidx.recyclerview.widget.RecyclerView.Recycler);
+ method public void removeAndRecycleView(android.view.View, androidx.recyclerview.widget.RecyclerView.Recycler);
+ method public void removeAndRecycleViewAt(int, androidx.recyclerview.widget.RecyclerView.Recycler);
+ method public boolean removeCallbacks(Runnable!);
+ method public void removeDetachedView(android.view.View);
+ method public void removeView(android.view.View!);
+ method public void removeViewAt(int);
+ method public boolean requestChildRectangleOnScreen(androidx.recyclerview.widget.RecyclerView, android.view.View, android.graphics.Rect, boolean);
+ method public boolean requestChildRectangleOnScreen(androidx.recyclerview.widget.RecyclerView, android.view.View, android.graphics.Rect, boolean, boolean);
+ method public void requestLayout();
+ method public void requestSimpleAnimationsInNextLayout();
+ method public int scrollHorizontallyBy(int, androidx.recyclerview.widget.RecyclerView.Recycler!, androidx.recyclerview.widget.RecyclerView.State!);
+ method public void scrollToPosition(int);
+ method public int scrollVerticallyBy(int, androidx.recyclerview.widget.RecyclerView.Recycler!, androidx.recyclerview.widget.RecyclerView.State!);
+ method @Deprecated public void setAutoMeasureEnabled(boolean);
+ method public final void setItemPrefetchEnabled(boolean);
+ method public void setMeasuredDimension(android.graphics.Rect!, int, int);
+ method public void setMeasuredDimension(int, int);
+ method public void setMeasurementCacheEnabled(boolean);
+ method public void smoothScrollToPosition(androidx.recyclerview.widget.RecyclerView!, androidx.recyclerview.widget.RecyclerView.State!, int);
+ method public void startSmoothScroll(androidx.recyclerview.widget.RecyclerView.SmoothScroller!);
+ method public void stopIgnoringView(android.view.View);
+ method public boolean supportsPredictiveItemAnimations();
+ }
+
+ public static interface RecyclerView.LayoutManager.LayoutPrefetchRegistry {
+ method public void addPosition(int, int);
+ }
+
+ public static class RecyclerView.LayoutManager.Properties {
+ ctor public RecyclerView.LayoutManager.Properties();
+ field public int orientation;
+ field public boolean reverseLayout;
+ field public int spanCount;
+ field public boolean stackFromEnd;
+ }
+
+ public static class RecyclerView.LayoutParams extends android.view.ViewGroup.MarginLayoutParams {
+ ctor public RecyclerView.LayoutParams(android.content.Context!, android.util.AttributeSet!);
+ ctor public RecyclerView.LayoutParams(int, int);
+ ctor public RecyclerView.LayoutParams(android.view.ViewGroup.MarginLayoutParams!);
+ ctor public RecyclerView.LayoutParams(android.view.ViewGroup.LayoutParams!);
+ ctor public RecyclerView.LayoutParams(androidx.recyclerview.widget.RecyclerView.LayoutParams!);
+ method public int getViewAdapterPosition();
+ method public int getViewLayoutPosition();
+ method @Deprecated public int getViewPosition();
+ method public boolean isItemChanged();
+ method public boolean isItemRemoved();
+ method public boolean isViewInvalid();
+ method public boolean viewNeedsUpdate();
+ }
+
+ public static interface RecyclerView.OnChildAttachStateChangeListener {
+ method public void onChildViewAttachedToWindow(android.view.View);
+ method public void onChildViewDetachedFromWindow(android.view.View);
+ }
+
+ public abstract static class RecyclerView.OnFlingListener {
+ ctor public RecyclerView.OnFlingListener();
+ method public abstract boolean onFling(int, int);
+ }
+
+ public static interface RecyclerView.OnItemTouchListener {
+ method public boolean onInterceptTouchEvent(androidx.recyclerview.widget.RecyclerView, android.view.MotionEvent);
+ method public void onRequestDisallowInterceptTouchEvent(boolean);
+ method public void onTouchEvent(androidx.recyclerview.widget.RecyclerView, android.view.MotionEvent);
+ }
+
+ public abstract static class RecyclerView.OnScrollListener {
+ ctor public RecyclerView.OnScrollListener();
+ method public void onScrollStateChanged(androidx.recyclerview.widget.RecyclerView, int);
+ method public void onScrolled(androidx.recyclerview.widget.RecyclerView, int, int);
+ }
+
+ public static class RecyclerView.RecycledViewPool {
+ ctor public RecyclerView.RecycledViewPool();
+ method public void clear();
+ method public androidx.recyclerview.widget.RecyclerView.ViewHolder? getRecycledView(int);
+ method public int getRecycledViewCount(int);
+ method public void putRecycledView(androidx.recyclerview.widget.RecyclerView.ViewHolder!);
+ method public void setMaxRecycledViews(int, int);
+ }
+
+ public final class RecyclerView.Recycler {
+ ctor public RecyclerView.Recycler();
+ method public void bindViewToPosition(android.view.View, int);
+ method public void clear();
+ method public int convertPreLayoutPositionToPostLayout(int);
+ method public java.util.List<androidx.recyclerview.widget.RecyclerView.ViewHolder!> getScrapList();
+ method public android.view.View getViewForPosition(int);
+ method public void recycleView(android.view.View);
+ method public void setViewCacheSize(int);
+ }
+
+ public static interface RecyclerView.RecyclerListener {
+ method public void onViewRecycled(androidx.recyclerview.widget.RecyclerView.ViewHolder);
+ }
+
+ public static class RecyclerView.SimpleOnItemTouchListener implements androidx.recyclerview.widget.RecyclerView.OnItemTouchListener {
+ ctor public RecyclerView.SimpleOnItemTouchListener();
+ method public boolean onInterceptTouchEvent(androidx.recyclerview.widget.RecyclerView, android.view.MotionEvent);
+ method public void onRequestDisallowInterceptTouchEvent(boolean);
+ method public void onTouchEvent(androidx.recyclerview.widget.RecyclerView, android.view.MotionEvent);
+ }
+
+ public abstract static class RecyclerView.SmoothScroller {
+ ctor public RecyclerView.SmoothScroller();
+ method public android.graphics.PointF? computeScrollVectorForPosition(int);
+ method public android.view.View! findViewByPosition(int);
+ method public int getChildCount();
+ method public int getChildPosition(android.view.View!);
+ method public androidx.recyclerview.widget.RecyclerView.LayoutManager? getLayoutManager();
+ method public int getTargetPosition();
+ method @Deprecated public void instantScrollToPosition(int);
+ method public boolean isPendingInitialRun();
+ method public boolean isRunning();
+ method protected void normalize(android.graphics.PointF);
+ method protected void onChildAttachedToWindow(android.view.View!);
+ method protected abstract void onSeekTargetStep(@Px int, @Px int, androidx.recyclerview.widget.RecyclerView.State, androidx.recyclerview.widget.RecyclerView.SmoothScroller.Action);
+ method protected abstract void onStart();
+ method protected abstract void onStop();
+ method protected abstract void onTargetFound(android.view.View, androidx.recyclerview.widget.RecyclerView.State, androidx.recyclerview.widget.RecyclerView.SmoothScroller.Action);
+ method public void setTargetPosition(int);
+ method protected final void stop();
+ }
+
+ public static class RecyclerView.SmoothScroller.Action {
+ ctor public RecyclerView.SmoothScroller.Action(@Px int, @Px int);
+ ctor public RecyclerView.SmoothScroller.Action(@Px int, @Px int, int);
+ ctor public RecyclerView.SmoothScroller.Action(@Px int, @Px int, int, android.view.animation.Interpolator?);
+ method public int getDuration();
+ method @Px public int getDx();
+ method @Px public int getDy();
+ method public android.view.animation.Interpolator? getInterpolator();
+ method public void jumpTo(int);
+ method public void setDuration(int);
+ method public void setDx(@Px int);
+ method public void setDy(@Px int);
+ method public void setInterpolator(android.view.animation.Interpolator?);
+ method public void update(@Px int, @Px int, int, android.view.animation.Interpolator?);
+ field public static final int UNDEFINED_DURATION = -2147483648; // 0x80000000
+ }
+
+ public static interface RecyclerView.SmoothScroller.ScrollVectorProvider {
+ method public android.graphics.PointF? computeScrollVectorForPosition(int);
+ }
+
+ public static class RecyclerView.State {
+ ctor public RecyclerView.State();
+ method public boolean didStructureChange();
+ method public <T> T! get(int);
+ method public int getItemCount();
+ method public int getRemainingScrollHorizontal();
+ method public int getRemainingScrollVertical();
+ method public int getTargetScrollPosition();
+ method public boolean hasTargetScrollPosition();
+ method public boolean isMeasuring();
+ method public boolean isPreLayout();
+ method public void put(int, Object!);
+ method public void remove(int);
+ method public boolean willRunPredictiveAnimations();
+ method public boolean willRunSimpleAnimations();
+ }
+
+ public abstract static class RecyclerView.ViewCacheExtension {
+ ctor public RecyclerView.ViewCacheExtension();
+ method public abstract android.view.View? getViewForPositionAndType(androidx.recyclerview.widget.RecyclerView.Recycler, int, int);
+ }
+
+ public abstract static class RecyclerView.ViewHolder {
+ ctor public RecyclerView.ViewHolder(android.view.View);
+ method public final int getAdapterPosition();
+ method public final long getItemId();
+ method public final int getItemViewType();
+ method public final int getLayoutPosition();
+ method public final int getOldPosition();
+ method @Deprecated public final int getPosition();
+ method public final boolean isRecyclable();
+ method public final void setIsRecyclable(boolean);
+ field public final android.view.View itemView;
+ }
+
+ public class RecyclerViewAccessibilityDelegate extends androidx.core.view.AccessibilityDelegateCompat {
+ ctor public RecyclerViewAccessibilityDelegate(androidx.recyclerview.widget.RecyclerView);
+ method public androidx.core.view.AccessibilityDelegateCompat getItemDelegate();
+ }
+
+ public static class RecyclerViewAccessibilityDelegate.ItemDelegate extends androidx.core.view.AccessibilityDelegateCompat {
+ ctor public RecyclerViewAccessibilityDelegate.ItemDelegate(androidx.recyclerview.widget.RecyclerViewAccessibilityDelegate);
+ }
+
+ public abstract class SimpleItemAnimator extends androidx.recyclerview.widget.RecyclerView.ItemAnimator {
+ ctor public SimpleItemAnimator();
+ method public abstract boolean animateAdd(androidx.recyclerview.widget.RecyclerView.ViewHolder!);
+ method public boolean animateAppearance(androidx.recyclerview.widget.RecyclerView.ViewHolder, androidx.recyclerview.widget.RecyclerView.ItemAnimator.ItemHolderInfo?, androidx.recyclerview.widget.RecyclerView.ItemAnimator.ItemHolderInfo);
+ method public boolean animateChange(androidx.recyclerview.widget.RecyclerView.ViewHolder, androidx.recyclerview.widget.RecyclerView.ViewHolder, androidx.recyclerview.widget.RecyclerView.ItemAnimator.ItemHolderInfo, androidx.recyclerview.widget.RecyclerView.ItemAnimator.ItemHolderInfo);
+ method public abstract boolean animateChange(androidx.recyclerview.widget.RecyclerView.ViewHolder!, androidx.recyclerview.widget.RecyclerView.ViewHolder!, int, int, int, int);
+ method public boolean animateDisappearance(androidx.recyclerview.widget.RecyclerView.ViewHolder, androidx.recyclerview.widget.RecyclerView.ItemAnimator.ItemHolderInfo, androidx.recyclerview.widget.RecyclerView.ItemAnimator.ItemHolderInfo?);
+ method public abstract boolean animateMove(androidx.recyclerview.widget.RecyclerView.ViewHolder!, int, int, int, int);
+ method public boolean animatePersistence(androidx.recyclerview.widget.RecyclerView.ViewHolder, androidx.recyclerview.widget.RecyclerView.ItemAnimator.ItemHolderInfo, androidx.recyclerview.widget.RecyclerView.ItemAnimator.ItemHolderInfo);
+ method public abstract boolean animateRemove(androidx.recyclerview.widget.RecyclerView.ViewHolder!);
+ method public final void dispatchAddFinished(androidx.recyclerview.widget.RecyclerView.ViewHolder!);
+ method public final void dispatchAddStarting(androidx.recyclerview.widget.RecyclerView.ViewHolder!);
+ method public final void dispatchChangeFinished(androidx.recyclerview.widget.RecyclerView.ViewHolder!, boolean);
+ method public final void dispatchChangeStarting(androidx.recyclerview.widget.RecyclerView.ViewHolder!, boolean);
+ method public final void dispatchMoveFinished(androidx.recyclerview.widget.RecyclerView.ViewHolder!);
+ method public final void dispatchMoveStarting(androidx.recyclerview.widget.RecyclerView.ViewHolder!);
+ method public final void dispatchRemoveFinished(androidx.recyclerview.widget.RecyclerView.ViewHolder!);
+ method public final void dispatchRemoveStarting(androidx.recyclerview.widget.RecyclerView.ViewHolder!);
+ method public boolean getSupportsChangeAnimations();
+ method public void onAddFinished(androidx.recyclerview.widget.RecyclerView.ViewHolder!);
+ method public void onAddStarting(androidx.recyclerview.widget.RecyclerView.ViewHolder!);
+ method public void onChangeFinished(androidx.recyclerview.widget.RecyclerView.ViewHolder!, boolean);
+ method public void onChangeStarting(androidx.recyclerview.widget.RecyclerView.ViewHolder!, boolean);
+ method public void onMoveFinished(androidx.recyclerview.widget.RecyclerView.ViewHolder!);
+ method public void onMoveStarting(androidx.recyclerview.widget.RecyclerView.ViewHolder!);
+ method public void onRemoveFinished(androidx.recyclerview.widget.RecyclerView.ViewHolder!);
+ method public void onRemoveStarting(androidx.recyclerview.widget.RecyclerView.ViewHolder!);
+ method public void setSupportsChangeAnimations(boolean);
+ }
+
+ public abstract class SnapHelper extends androidx.recyclerview.widget.RecyclerView.OnFlingListener {
+ ctor public SnapHelper();
+ method public void attachToRecyclerView(androidx.recyclerview.widget.RecyclerView?) throws java.lang.IllegalStateException;
+ method public abstract int[]? calculateDistanceToFinalSnap(androidx.recyclerview.widget.RecyclerView.LayoutManager, android.view.View);
+ method public int[]! calculateScrollDistance(int, int);
+ method protected androidx.recyclerview.widget.RecyclerView.SmoothScroller? createScroller(androidx.recyclerview.widget.RecyclerView.LayoutManager!);
+ method @Deprecated protected androidx.recyclerview.widget.LinearSmoothScroller? createSnapScroller(androidx.recyclerview.widget.RecyclerView.LayoutManager!);
+ method public abstract android.view.View? findSnapView(androidx.recyclerview.widget.RecyclerView.LayoutManager!);
+ method public abstract int findTargetSnapPosition(androidx.recyclerview.widget.RecyclerView.LayoutManager!, int, int);
+ method public boolean onFling(int, int);
+ }
+
+ public class SortedList<T> {
+ ctor public SortedList(Class<T!>, androidx.recyclerview.widget.SortedList.Callback<T!>);
+ ctor public SortedList(Class<T!>, androidx.recyclerview.widget.SortedList.Callback<T!>, int);
+ method public int add(T!);
+ method public void addAll(T![], boolean);
+ method public void addAll(T!...);
+ method public void addAll(java.util.Collection<T!>);
+ method public void beginBatchedUpdates();
+ method public void clear();
+ method public void endBatchedUpdates();
+ method public T! get(int) throws java.lang.IndexOutOfBoundsException;
+ method public int indexOf(T!);
+ method public void recalculatePositionOfItemAt(int);
+ method public boolean remove(T!);
+ method public T! removeItemAt(int);
+ method public void replaceAll(T![], boolean);
+ method public void replaceAll(T!...);
+ method public void replaceAll(java.util.Collection<T!>);
+ method public int size();
+ method public void updateItemAt(int, T!);
+ field public static final int INVALID_POSITION = -1; // 0xffffffff
+ }
+
+ public static class SortedList.BatchedCallback<T2> extends androidx.recyclerview.widget.SortedList.Callback<T2> {
+ ctor public SortedList.BatchedCallback(androidx.recyclerview.widget.SortedList.Callback<T2!>!);
+ method public boolean areContentsTheSame(T2!, T2!);
+ method public boolean areItemsTheSame(T2!, T2!);
+ method public int compare(T2!, T2!);
+ method public void dispatchLastEvent();
+ method public void onChanged(int, int);
+ method public void onInserted(int, int);
+ method public void onMoved(int, int);
+ method public void onRemoved(int, int);
+ }
+
+ public abstract static class SortedList.Callback<T2> implements java.util.Comparator<T2> androidx.recyclerview.widget.ListUpdateCallback {
+ ctor public SortedList.Callback();
+ method public abstract boolean areContentsTheSame(T2!, T2!);
+ method public abstract boolean areItemsTheSame(T2!, T2!);
+ method public abstract int compare(T2!, T2!);
+ method public Object? getChangePayload(T2!, T2!);
+ method public abstract void onChanged(int, int);
+ method public void onChanged(int, int, Object!);
+ }
+
+ public abstract class SortedListAdapterCallback<T2> extends androidx.recyclerview.widget.SortedList.Callback<T2> {
+ ctor public SortedListAdapterCallback(androidx.recyclerview.widget.RecyclerView.Adapter!);
+ method public void onChanged(int, int);
+ method public void onInserted(int, int);
+ method public void onMoved(int, int);
+ method public void onRemoved(int, int);
+ }
+
+ public class StaggeredGridLayoutManager extends androidx.recyclerview.widget.RecyclerView.LayoutManager implements androidx.recyclerview.widget.RecyclerView.SmoothScroller.ScrollVectorProvider {
+ ctor public StaggeredGridLayoutManager(android.content.Context!, android.util.AttributeSet!, int, int);
+ ctor public StaggeredGridLayoutManager(int, int);
+ method public android.graphics.PointF! computeScrollVectorForPosition(int);
+ method public int[]! findFirstCompletelyVisibleItemPositions(int[]!);
+ method public int[]! findFirstVisibleItemPositions(int[]!);
+ method public int[]! findLastCompletelyVisibleItemPositions(int[]!);
+ method public int[]! findLastVisibleItemPositions(int[]!);
+ method public androidx.recyclerview.widget.RecyclerView.LayoutParams! generateDefaultLayoutParams();
+ method public int getGapStrategy();
+ method public int getOrientation();
+ method public boolean getReverseLayout();
+ method public int getSpanCount();
+ method public void invalidateSpanAssignments();
+ method public void scrollToPositionWithOffset(int, int);
+ method public void setGapStrategy(int);
+ method public void setOrientation(int);
+ method public void setReverseLayout(boolean);
+ method public void setSpanCount(int);
+ field @Deprecated public static final int GAP_HANDLING_LAZY = 1; // 0x1
+ field public static final int GAP_HANDLING_MOVE_ITEMS_BETWEEN_SPANS = 2; // 0x2
+ field public static final int GAP_HANDLING_NONE = 0; // 0x0
+ field public static final int HORIZONTAL = 0; // 0x0
+ field public static final int VERTICAL = 1; // 0x1
+ }
+
+ public static class StaggeredGridLayoutManager.LayoutParams extends androidx.recyclerview.widget.RecyclerView.LayoutParams {
+ ctor public StaggeredGridLayoutManager.LayoutParams(android.content.Context!, android.util.AttributeSet!);
+ ctor public StaggeredGridLayoutManager.LayoutParams(int, int);
+ ctor public StaggeredGridLayoutManager.LayoutParams(android.view.ViewGroup.MarginLayoutParams!);
+ ctor public StaggeredGridLayoutManager.LayoutParams(android.view.ViewGroup.LayoutParams!);
+ ctor public StaggeredGridLayoutManager.LayoutParams(androidx.recyclerview.widget.RecyclerView.LayoutParams!);
+ method public final int getSpanIndex();
+ method public boolean isFullSpan();
+ method public void setFullSpan(boolean);
+ field public static final int INVALID_SPAN_ID = -1; // 0xffffffff
+ }
+
+}
+
diff --git a/recyclerview/recyclerview/api/res-1.1.0-beta04.txt b/recyclerview/recyclerview/api/res-1.1.0-beta04.txt
new file mode 100644
index 0000000..475bfc43
--- /dev/null
+++ b/recyclerview/recyclerview/api/res-1.1.0-beta04.txt
@@ -0,0 +1,9 @@
+attr fastScrollEnabled
+attr fastScrollHorizontalThumbDrawable
+attr fastScrollHorizontalTrackDrawable
+attr fastScrollVerticalThumbDrawable
+attr fastScrollVerticalTrackDrawable
+attr layoutManager
+attr reverseLayout
+attr spanCount
+attr stackFromEnd
diff --git a/recyclerview/recyclerview/api/restricted_1.1.0-beta04.txt b/recyclerview/recyclerview/api/restricted_1.1.0-beta04.txt
new file mode 100644
index 0000000..0690168
--- /dev/null
+++ b/recyclerview/recyclerview/api/restricted_1.1.0-beta04.txt
@@ -0,0 +1,1043 @@
+// Signature format: 3.0
+package androidx.recyclerview.widget {
+
+ public final class AdapterListUpdateCallback implements androidx.recyclerview.widget.ListUpdateCallback {
+ ctor public AdapterListUpdateCallback(androidx.recyclerview.widget.RecyclerView.Adapter);
+ method public void onChanged(int, int, Object!);
+ method public void onInserted(int, int);
+ method public void onMoved(int, int);
+ method public void onRemoved(int, int);
+ }
+
+ public final class AsyncDifferConfig<T> {
+ method public java.util.concurrent.Executor getBackgroundThreadExecutor();
+ method public androidx.recyclerview.widget.DiffUtil.ItemCallback<T!> getDiffCallback();
+ }
+
+ public static final class AsyncDifferConfig.Builder<T> {
+ ctor public AsyncDifferConfig.Builder(androidx.recyclerview.widget.DiffUtil.ItemCallback<T!>);
+ method public androidx.recyclerview.widget.AsyncDifferConfig<T!> build();
+ method public androidx.recyclerview.widget.AsyncDifferConfig.Builder<T!> setBackgroundThreadExecutor(java.util.concurrent.Executor!);
+ }
+
+ public class AsyncListDiffer<T> {
+ ctor public AsyncListDiffer(androidx.recyclerview.widget.RecyclerView.Adapter, androidx.recyclerview.widget.DiffUtil.ItemCallback<T!>);
+ ctor public AsyncListDiffer(androidx.recyclerview.widget.ListUpdateCallback, androidx.recyclerview.widget.AsyncDifferConfig<T!>);
+ method public void addListListener(androidx.recyclerview.widget.AsyncListDiffer.ListListener<T!>);
+ method public java.util.List<T!> getCurrentList();
+ method public void removeListListener(androidx.recyclerview.widget.AsyncListDiffer.ListListener<T!>);
+ method public void submitList(java.util.List<T!>?);
+ method public void submitList(java.util.List<T!>?, Runnable?);
+ }
+
+ public static interface AsyncListDiffer.ListListener<T> {
+ method public void onCurrentListChanged(java.util.List<T!>, java.util.List<T!>);
+ }
+
+ public class AsyncListUtil<T> {
+ ctor public AsyncListUtil(Class<T!>, int, androidx.recyclerview.widget.AsyncListUtil.DataCallback<T!>, androidx.recyclerview.widget.AsyncListUtil.ViewCallback);
+ method public T? getItem(int);
+ method public int getItemCount();
+ method public void onRangeChanged();
+ method public void refresh();
+ }
+
+ public abstract static class AsyncListUtil.DataCallback<T> {
+ ctor public AsyncListUtil.DataCallback();
+ method @WorkerThread public abstract void fillData(T![], int, int);
+ method @WorkerThread public int getMaxCachedTiles();
+ method @WorkerThread public void recycleData(T![], int);
+ method @WorkerThread public abstract int refreshData();
+ }
+
+ public abstract static class AsyncListUtil.ViewCallback {
+ ctor public AsyncListUtil.ViewCallback();
+ method @UiThread public void extendRangeInto(int[], int[], int);
+ method @UiThread public abstract void getItemRangeInto(int[]);
+ method @UiThread public abstract void onDataRefresh();
+ method @UiThread public abstract void onItemLoaded(int);
+ field public static final int HINT_SCROLL_ASC = 2; // 0x2
+ field public static final int HINT_SCROLL_DESC = 1; // 0x1
+ field public static final int HINT_SCROLL_NONE = 0; // 0x0
+ }
+
+ public class BatchingListUpdateCallback implements androidx.recyclerview.widget.ListUpdateCallback {
+ ctor public BatchingListUpdateCallback(androidx.recyclerview.widget.ListUpdateCallback);
+ method public void dispatchLastEvent();
+ method public void onChanged(int, int, Object!);
+ method public void onInserted(int, int);
+ method public void onMoved(int, int);
+ method public void onRemoved(int, int);
+ }
+
+ public class DefaultItemAnimator extends androidx.recyclerview.widget.SimpleItemAnimator {
+ ctor public DefaultItemAnimator();
+ method public boolean animateAdd(androidx.recyclerview.widget.RecyclerView.ViewHolder!);
+ method public boolean animateChange(androidx.recyclerview.widget.RecyclerView.ViewHolder!, androidx.recyclerview.widget.RecyclerView.ViewHolder!, int, int, int, int);
+ method public boolean animateMove(androidx.recyclerview.widget.RecyclerView.ViewHolder!, int, int, int, int);
+ method public boolean animateRemove(androidx.recyclerview.widget.RecyclerView.ViewHolder!);
+ method public void endAnimation(androidx.recyclerview.widget.RecyclerView.ViewHolder!);
+ method public void endAnimations();
+ method public boolean isRunning();
+ method public void runPendingAnimations();
+ }
+
+ public class DiffUtil {
+ method public static androidx.recyclerview.widget.DiffUtil.DiffResult calculateDiff(androidx.recyclerview.widget.DiffUtil.Callback);
+ method public static androidx.recyclerview.widget.DiffUtil.DiffResult calculateDiff(androidx.recyclerview.widget.DiffUtil.Callback, boolean);
+ }
+
+ public abstract static class DiffUtil.Callback {
+ ctor public DiffUtil.Callback();
+ method public abstract boolean areContentsTheSame(int, int);
+ method public abstract boolean areItemsTheSame(int, int);
+ method public Object? getChangePayload(int, int);
+ method public abstract int getNewListSize();
+ method public abstract int getOldListSize();
+ }
+
+ public static class DiffUtil.DiffResult {
+ method public int convertNewPositionToOld(@IntRange(from=0) int);
+ method public int convertOldPositionToNew(@IntRange(from=0) int);
+ method public void dispatchUpdatesTo(androidx.recyclerview.widget.RecyclerView.Adapter);
+ method public void dispatchUpdatesTo(androidx.recyclerview.widget.ListUpdateCallback);
+ field public static final int NO_POSITION = -1; // 0xffffffff
+ }
+
+ public abstract static class DiffUtil.ItemCallback<T> {
+ ctor public DiffUtil.ItemCallback();
+ method public abstract boolean areContentsTheSame(T, T);
+ method public abstract boolean areItemsTheSame(T, T);
+ method public Object? getChangePayload(T, T);
+ }
+
+ public class DividerItemDecoration extends androidx.recyclerview.widget.RecyclerView.ItemDecoration {
+ ctor public DividerItemDecoration(android.content.Context!, int);
+ method public android.graphics.drawable.Drawable? getDrawable();
+ method public void setDrawable(android.graphics.drawable.Drawable);
+ method public void setOrientation(int);
+ field public static final int HORIZONTAL = 0; // 0x0
+ field public static final int VERTICAL = 1; // 0x1
+ }
+
+ public class GridLayoutManager extends androidx.recyclerview.widget.LinearLayoutManager {
+ ctor public GridLayoutManager(android.content.Context!, android.util.AttributeSet!, int, int);
+ ctor public GridLayoutManager(android.content.Context!, int);
+ ctor public GridLayoutManager(android.content.Context!, int, @androidx.recyclerview.widget.RecyclerView.Orientation int, boolean);
+ method public int getSpanCount();
+ method public androidx.recyclerview.widget.GridLayoutManager.SpanSizeLookup! getSpanSizeLookup();
+ method public boolean isUsingSpansToEstimateScrollbarDimensions();
+ method public void setSpanCount(int);
+ method public void setSpanSizeLookup(androidx.recyclerview.widget.GridLayoutManager.SpanSizeLookup!);
+ method public void setUsingSpansToEstimateScrollbarDimensions(boolean);
+ field public static final int DEFAULT_SPAN_COUNT = -1; // 0xffffffff
+ }
+
+ public static final class GridLayoutManager.DefaultSpanSizeLookup extends androidx.recyclerview.widget.GridLayoutManager.SpanSizeLookup {
+ ctor public GridLayoutManager.DefaultSpanSizeLookup();
+ method public int getSpanSize(int);
+ }
+
+ public static class GridLayoutManager.LayoutParams extends androidx.recyclerview.widget.RecyclerView.LayoutParams {
+ ctor public GridLayoutManager.LayoutParams(android.content.Context!, android.util.AttributeSet!);
+ ctor public GridLayoutManager.LayoutParams(int, int);
+ ctor public GridLayoutManager.LayoutParams(android.view.ViewGroup.MarginLayoutParams!);
+ ctor public GridLayoutManager.LayoutParams(android.view.ViewGroup.LayoutParams!);
+ ctor public GridLayoutManager.LayoutParams(androidx.recyclerview.widget.RecyclerView.LayoutParams!);
+ method public int getSpanIndex();
+ method public int getSpanSize();
+ field public static final int INVALID_SPAN_ID = -1; // 0xffffffff
+ }
+
+ public abstract static class GridLayoutManager.SpanSizeLookup {
+ ctor public GridLayoutManager.SpanSizeLookup();
+ method public int getSpanGroupIndex(int, int);
+ method public int getSpanIndex(int, int);
+ method public abstract int getSpanSize(int);
+ method public void invalidateSpanGroupIndexCache();
+ method public void invalidateSpanIndexCache();
+ method public boolean isSpanGroupIndexCacheEnabled();
+ method public boolean isSpanIndexCacheEnabled();
+ method public void setSpanGroupIndexCacheEnabled(boolean);
+ method public void setSpanIndexCacheEnabled(boolean);
+ }
+
+ public class ItemTouchHelper extends androidx.recyclerview.widget.RecyclerView.ItemDecoration implements androidx.recyclerview.widget.RecyclerView.OnChildAttachStateChangeListener {
+ ctor public ItemTouchHelper(androidx.recyclerview.widget.ItemTouchHelper.Callback);
+ method public void attachToRecyclerView(androidx.recyclerview.widget.RecyclerView?);
+ method public void onChildViewAttachedToWindow(android.view.View);
+ method public void onChildViewDetachedFromWindow(android.view.View);
+ method public void startDrag(androidx.recyclerview.widget.RecyclerView.ViewHolder);
+ method public void startSwipe(androidx.recyclerview.widget.RecyclerView.ViewHolder);
+ field public static final int ACTION_STATE_DRAG = 2; // 0x2
+ field public static final int ACTION_STATE_IDLE = 0; // 0x0
+ field public static final int ACTION_STATE_SWIPE = 1; // 0x1
+ field public static final int ANIMATION_TYPE_DRAG = 8; // 0x8
+ field public static final int ANIMATION_TYPE_SWIPE_CANCEL = 4; // 0x4
+ field public static final int ANIMATION_TYPE_SWIPE_SUCCESS = 2; // 0x2
+ field public static final int DOWN = 2; // 0x2
+ field public static final int END = 32; // 0x20
+ field public static final int LEFT = 4; // 0x4
+ field public static final int RIGHT = 8; // 0x8
+ field public static final int START = 16; // 0x10
+ field public static final int UP = 1; // 0x1
+ }
+
+ public abstract static class ItemTouchHelper.Callback {
+ ctor public ItemTouchHelper.Callback();
+ method public boolean canDropOver(androidx.recyclerview.widget.RecyclerView, androidx.recyclerview.widget.RecyclerView.ViewHolder, androidx.recyclerview.widget.RecyclerView.ViewHolder);
+ method public androidx.recyclerview.widget.RecyclerView.ViewHolder! chooseDropTarget(androidx.recyclerview.widget.RecyclerView.ViewHolder, java.util.List<androidx.recyclerview.widget.RecyclerView.ViewHolder!>, int, int);
+ method public void clearView(androidx.recyclerview.widget.RecyclerView, androidx.recyclerview.widget.RecyclerView.ViewHolder);
+ method public int convertToAbsoluteDirection(int, int);
+ method public static int convertToRelativeDirection(int, int);
+ method public long getAnimationDuration(androidx.recyclerview.widget.RecyclerView, int, float, float);
+ method public int getBoundingBoxMargin();
+ method public static androidx.recyclerview.widget.ItemTouchUIUtil getDefaultUIUtil();
+ method public float getMoveThreshold(androidx.recyclerview.widget.RecyclerView.ViewHolder);
+ method public abstract int getMovementFlags(androidx.recyclerview.widget.RecyclerView, androidx.recyclerview.widget.RecyclerView.ViewHolder);
+ method public float getSwipeEscapeVelocity(float);
+ method public float getSwipeThreshold(androidx.recyclerview.widget.RecyclerView.ViewHolder);
+ method public float getSwipeVelocityThreshold(float);
+ method public int interpolateOutOfBoundsScroll(androidx.recyclerview.widget.RecyclerView, int, int, int, long);
+ method public boolean isItemViewSwipeEnabled();
+ method public boolean isLongPressDragEnabled();
+ method public static int makeFlag(int, int);
+ method public static int makeMovementFlags(int, int);
+ method public void onChildDraw(android.graphics.Canvas, androidx.recyclerview.widget.RecyclerView, androidx.recyclerview.widget.RecyclerView.ViewHolder, float, float, int, boolean);
+ method public void onChildDrawOver(android.graphics.Canvas, androidx.recyclerview.widget.RecyclerView, androidx.recyclerview.widget.RecyclerView.ViewHolder!, float, float, int, boolean);
+ method public abstract boolean onMove(androidx.recyclerview.widget.RecyclerView, androidx.recyclerview.widget.RecyclerView.ViewHolder, androidx.recyclerview.widget.RecyclerView.ViewHolder);
+ method public void onMoved(androidx.recyclerview.widget.RecyclerView, androidx.recyclerview.widget.RecyclerView.ViewHolder, int, androidx.recyclerview.widget.RecyclerView.ViewHolder, int, int, int);
+ method public void onSelectedChanged(androidx.recyclerview.widget.RecyclerView.ViewHolder?, int);
+ method public abstract void onSwiped(androidx.recyclerview.widget.RecyclerView.ViewHolder, int);
+ field public static final int DEFAULT_DRAG_ANIMATION_DURATION = 200; // 0xc8
+ field public static final int DEFAULT_SWIPE_ANIMATION_DURATION = 250; // 0xfa
+ }
+
+ public abstract static class ItemTouchHelper.SimpleCallback extends androidx.recyclerview.widget.ItemTouchHelper.Callback {
+ ctor public ItemTouchHelper.SimpleCallback(int, int);
+ method public int getDragDirs(androidx.recyclerview.widget.RecyclerView, androidx.recyclerview.widget.RecyclerView.ViewHolder);
+ method public int getMovementFlags(androidx.recyclerview.widget.RecyclerView, androidx.recyclerview.widget.RecyclerView.ViewHolder);
+ method public int getSwipeDirs(androidx.recyclerview.widget.RecyclerView, androidx.recyclerview.widget.RecyclerView.ViewHolder);
+ method public void setDefaultDragDirs(int);
+ method public void setDefaultSwipeDirs(int);
+ }
+
+ public static interface ItemTouchHelper.ViewDropHandler {
+ method public void prepareForDrop(android.view.View, android.view.View, int, int);
+ }
+
+ public interface ItemTouchUIUtil {
+ method public void clearView(android.view.View!);
+ method public void onDraw(android.graphics.Canvas!, androidx.recyclerview.widget.RecyclerView!, android.view.View!, float, float, int, boolean);
+ method public void onDrawOver(android.graphics.Canvas!, androidx.recyclerview.widget.RecyclerView!, android.view.View!, float, float, int, boolean);
+ method public void onSelected(android.view.View!);
+ }
+
+ public class LinearLayoutManager extends androidx.recyclerview.widget.RecyclerView.LayoutManager implements androidx.recyclerview.widget.ItemTouchHelper.ViewDropHandler androidx.recyclerview.widget.RecyclerView.SmoothScroller.ScrollVectorProvider {
+ ctor public LinearLayoutManager(android.content.Context!);
+ ctor public LinearLayoutManager(android.content.Context!, @androidx.recyclerview.widget.RecyclerView.Orientation int, boolean);
+ ctor public LinearLayoutManager(android.content.Context!, android.util.AttributeSet!, int, int);
+ method protected void calculateExtraLayoutSpace(androidx.recyclerview.widget.RecyclerView.State, int[]);
+ method public android.graphics.PointF! computeScrollVectorForPosition(int);
+ method public int findFirstCompletelyVisibleItemPosition();
+ method public int findFirstVisibleItemPosition();
+ method public int findLastCompletelyVisibleItemPosition();
+ method public int findLastVisibleItemPosition();
+ method public androidx.recyclerview.widget.RecyclerView.LayoutParams! generateDefaultLayoutParams();
+ method @Deprecated protected int getExtraLayoutSpace(androidx.recyclerview.widget.RecyclerView.State!);
+ method public int getInitialPrefetchItemCount();
+ method @androidx.recyclerview.widget.RecyclerView.Orientation public int getOrientation();
+ method public boolean getRecycleChildrenOnDetach();
+ method public boolean getReverseLayout();
+ method public boolean getStackFromEnd();
+ method protected boolean isLayoutRTL();
+ method public boolean isSmoothScrollbarEnabled();
+ method public void prepareForDrop(android.view.View, android.view.View, int, int);
+ method public void scrollToPositionWithOffset(int, int);
+ method public void setInitialPrefetchItemCount(int);
+ method public void setOrientation(@androidx.recyclerview.widget.RecyclerView.Orientation int);
+ method public void setRecycleChildrenOnDetach(boolean);
+ method public void setReverseLayout(boolean);
+ method public void setSmoothScrollbarEnabled(boolean);
+ method public void setStackFromEnd(boolean);
+ field public static final int HORIZONTAL = 0; // 0x0
+ field public static final int INVALID_OFFSET = -2147483648; // 0x80000000
+ field public static final int VERTICAL = 1; // 0x1
+ }
+
+ protected static class LinearLayoutManager.LayoutChunkResult {
+ ctor protected LinearLayoutManager.LayoutChunkResult();
+ field public int mConsumed;
+ field public boolean mFinished;
+ field public boolean mFocusable;
+ field public boolean mIgnoreConsumed;
+ }
+
+
+ public class LinearSmoothScroller extends androidx.recyclerview.widget.RecyclerView.SmoothScroller {
+ ctor public LinearSmoothScroller(android.content.Context!);
+ method public int calculateDtToFit(int, int, int, int, int);
+ method public int calculateDxToMakeVisible(android.view.View!, int);
+ method public int calculateDyToMakeVisible(android.view.View!, int);
+ method protected float calculateSpeedPerPixel(android.util.DisplayMetrics!);
+ method protected int calculateTimeForDeceleration(int);
+ method protected int calculateTimeForScrolling(int);
+ method protected int getHorizontalSnapPreference();
+ method protected int getVerticalSnapPreference();
+ method protected void onSeekTargetStep(int, int, androidx.recyclerview.widget.RecyclerView.State!, androidx.recyclerview.widget.RecyclerView.SmoothScroller.Action!);
+ method protected void onStart();
+ method protected void onStop();
+ method protected void onTargetFound(android.view.View!, androidx.recyclerview.widget.RecyclerView.State!, androidx.recyclerview.widget.RecyclerView.SmoothScroller.Action!);
+ method protected void updateActionForInterimTarget(androidx.recyclerview.widget.RecyclerView.SmoothScroller.Action!);
+ field public static final int SNAP_TO_ANY = 0; // 0x0
+ field public static final int SNAP_TO_END = 1; // 0x1
+ field public static final int SNAP_TO_START = -1; // 0xffffffff
+ field protected final android.view.animation.DecelerateInterpolator! mDecelerateInterpolator;
+ field protected int mInterimTargetDx;
+ field protected int mInterimTargetDy;
+ field protected final android.view.animation.LinearInterpolator! mLinearInterpolator;
+ field protected android.graphics.PointF! mTargetVector;
+ }
+
+ public class LinearSnapHelper extends androidx.recyclerview.widget.SnapHelper {
+ ctor public LinearSnapHelper();
+ method public int[]! calculateDistanceToFinalSnap(androidx.recyclerview.widget.RecyclerView.LayoutManager, android.view.View);
+ method public android.view.View! findSnapView(androidx.recyclerview.widget.RecyclerView.LayoutManager!);
+ method public int findTargetSnapPosition(androidx.recyclerview.widget.RecyclerView.LayoutManager!, int, int);
+ }
+
+ public abstract class ListAdapter<T, VH extends androidx.recyclerview.widget.RecyclerView.ViewHolder> extends androidx.recyclerview.widget.RecyclerView.Adapter<VH> {
+ ctor protected ListAdapter(androidx.recyclerview.widget.DiffUtil.ItemCallback<T!>);
+ ctor protected ListAdapter(androidx.recyclerview.widget.AsyncDifferConfig<T!>);
+ method public java.util.List<T!> getCurrentList();
+ method protected T! getItem(int);
+ method public int getItemCount();
+ method public void onCurrentListChanged(java.util.List<T!>, java.util.List<T!>);
+ method public void submitList(java.util.List<T!>?);
+ method public void submitList(java.util.List<T!>?, Runnable?);
+ }
+
+ public interface ListUpdateCallback {
+ method public void onChanged(int, int, Object?);
+ method public void onInserted(int, int);
+ method public void onMoved(int, int);
+ method public void onRemoved(int, int);
+ }
+
+ public abstract class OrientationHelper {
+ method public static androidx.recyclerview.widget.OrientationHelper! createHorizontalHelper(androidx.recyclerview.widget.RecyclerView.LayoutManager!);
+ method public static androidx.recyclerview.widget.OrientationHelper! createOrientationHelper(androidx.recyclerview.widget.RecyclerView.LayoutManager!, @androidx.recyclerview.widget.RecyclerView.Orientation int);
+ method public static androidx.recyclerview.widget.OrientationHelper! createVerticalHelper(androidx.recyclerview.widget.RecyclerView.LayoutManager!);
+ method public abstract int getDecoratedEnd(android.view.View!);
+ method public abstract int getDecoratedMeasurement(android.view.View!);
+ method public abstract int getDecoratedMeasurementInOther(android.view.View!);
+ method public abstract int getDecoratedStart(android.view.View!);
+ method public abstract int getEnd();
+ method public abstract int getEndAfterPadding();
+ method public abstract int getEndPadding();
+ method public androidx.recyclerview.widget.RecyclerView.LayoutManager! getLayoutManager();
+ method public abstract int getMode();
+ method public abstract int getModeInOther();
+ method public abstract int getStartAfterPadding();
+ method public abstract int getTotalSpace();
+ method public int getTotalSpaceChange();
+ method public abstract int getTransformedEndWithDecoration(android.view.View!);
+ method public abstract int getTransformedStartWithDecoration(android.view.View!);
+ method public abstract void offsetChild(android.view.View!, int);
+ method public abstract void offsetChildren(int);
+ method public void onLayoutComplete();
+ field public static final int HORIZONTAL = 0; // 0x0
+ field public static final int VERTICAL = 1; // 0x1
+ field protected final androidx.recyclerview.widget.RecyclerView.LayoutManager! mLayoutManager;
+ }
+
+ public class PagerSnapHelper extends androidx.recyclerview.widget.SnapHelper {
+ ctor public PagerSnapHelper();
+ method public int[]? calculateDistanceToFinalSnap(androidx.recyclerview.widget.RecyclerView.LayoutManager, android.view.View);
+ method protected androidx.recyclerview.widget.LinearSmoothScroller! createSnapScroller(androidx.recyclerview.widget.RecyclerView.LayoutManager!);
+ method public android.view.View? findSnapView(androidx.recyclerview.widget.RecyclerView.LayoutManager!);
+ method public int findTargetSnapPosition(androidx.recyclerview.widget.RecyclerView.LayoutManager!, int, int);
+ }
+
+ public class RecyclerView extends android.view.ViewGroup implements androidx.core.view.NestedScrollingChild2 androidx.core.view.NestedScrollingChild3 androidx.core.view.ScrollingView {
+ ctor public RecyclerView(android.content.Context);
+ ctor public RecyclerView(android.content.Context, android.util.AttributeSet?);
+ ctor public RecyclerView(android.content.Context, android.util.AttributeSet?, int);
+ method public void addItemDecoration(androidx.recyclerview.widget.RecyclerView.ItemDecoration, int);
+ method public void addItemDecoration(androidx.recyclerview.widget.RecyclerView.ItemDecoration);
+ method public void addOnChildAttachStateChangeListener(androidx.recyclerview.widget.RecyclerView.OnChildAttachStateChangeListener);
+ method public void addOnItemTouchListener(androidx.recyclerview.widget.RecyclerView.OnItemTouchListener);
+ method public void addOnScrollListener(androidx.recyclerview.widget.RecyclerView.OnScrollListener);
+ method public void clearOnChildAttachStateChangeListeners();
+ method public void clearOnScrollListeners();
+ method public int computeHorizontalScrollExtent();
+ method public int computeHorizontalScrollOffset();
+ method public int computeHorizontalScrollRange();
+ method public int computeVerticalScrollExtent();
+ method public int computeVerticalScrollOffset();
+ method public int computeVerticalScrollRange();
+ method public boolean dispatchNestedPreScroll(int, int, int[]!, int[]!, int);
+ method public boolean dispatchNestedScroll(int, int, int, int, int[]!, int);
+ method public final void dispatchNestedScroll(int, int, int, int, int[]!, int, int[]);
+ method public boolean drawChild(android.graphics.Canvas!, android.view.View!, long);
+ method public android.view.View? findChildViewUnder(float, float);
+ method public android.view.View? findContainingItemView(android.view.View);
+ method public androidx.recyclerview.widget.RecyclerView.ViewHolder? findContainingViewHolder(android.view.View);
+ method public androidx.recyclerview.widget.RecyclerView.ViewHolder? findViewHolderForAdapterPosition(int);
+ method public androidx.recyclerview.widget.RecyclerView.ViewHolder! findViewHolderForItemId(long);
+ method public androidx.recyclerview.widget.RecyclerView.ViewHolder? findViewHolderForLayoutPosition(int);
+ method @Deprecated public androidx.recyclerview.widget.RecyclerView.ViewHolder? findViewHolderForPosition(int);
+ method public boolean fling(int, int);
+ method public androidx.recyclerview.widget.RecyclerView.Adapter? getAdapter();
+ method public int getChildAdapterPosition(android.view.View);
+ method public long getChildItemId(android.view.View);
+ method public int getChildLayoutPosition(android.view.View);
+ method @Deprecated public int getChildPosition(android.view.View);
+ method public androidx.recyclerview.widget.RecyclerView.ViewHolder! getChildViewHolder(android.view.View);
+ method public androidx.recyclerview.widget.RecyclerViewAccessibilityDelegate? getCompatAccessibilityDelegate();
+ method public void getDecoratedBoundsWithMargins(android.view.View, android.graphics.Rect);
+ method public androidx.recyclerview.widget.RecyclerView.EdgeEffectFactory getEdgeEffectFactory();
+ method public androidx.recyclerview.widget.RecyclerView.ItemAnimator? getItemAnimator();
+ method public androidx.recyclerview.widget.RecyclerView.ItemDecoration getItemDecorationAt(int);
+ method public int getItemDecorationCount();
+ method public androidx.recyclerview.widget.RecyclerView.LayoutManager? getLayoutManager();
+ method public int getMaxFlingVelocity();
+ method public int getMinFlingVelocity();
+ method public androidx.recyclerview.widget.RecyclerView.OnFlingListener? getOnFlingListener();
+ method public boolean getPreserveFocusAfterLayout();
+ method public androidx.recyclerview.widget.RecyclerView.RecycledViewPool getRecycledViewPool();
+ method public int getScrollState();
+ method public boolean hasFixedSize();
+ method public boolean hasNestedScrollingParent(int);
+ method public boolean hasPendingAdapterUpdates();
+ method public void invalidateItemDecorations();
+ method public boolean isAnimating();
+ method public boolean isComputingLayout();
+ method @Deprecated public boolean isLayoutFrozen();
+ method public final boolean isLayoutSuppressed();
+ method public void offsetChildrenHorizontal(@Px int);
+ method public void offsetChildrenVertical(@Px int);
+ method public void onChildAttachedToWindow(android.view.View);
+ method public void onChildDetachedFromWindow(android.view.View);
+ method public void onDraw(android.graphics.Canvas!);
+ method public void onScrollStateChanged(int);
+ method public void onScrolled(@Px int, @Px int);
+ method public void removeItemDecoration(androidx.recyclerview.widget.RecyclerView.ItemDecoration);
+ method public void removeItemDecorationAt(int);
+ method public void removeOnChildAttachStateChangeListener(androidx.recyclerview.widget.RecyclerView.OnChildAttachStateChangeListener);
+ method public void removeOnItemTouchListener(androidx.recyclerview.widget.RecyclerView.OnItemTouchListener);
+ method public void removeOnScrollListener(androidx.recyclerview.widget.RecyclerView.OnScrollListener);
+ method public void scrollToPosition(int);
+ method public void setAccessibilityDelegateCompat(androidx.recyclerview.widget.RecyclerViewAccessibilityDelegate?);
+ method public void setAdapter(androidx.recyclerview.widget.RecyclerView.Adapter?);
+ method public void setChildDrawingOrderCallback(androidx.recyclerview.widget.RecyclerView.ChildDrawingOrderCallback?);
+ method public void setEdgeEffectFactory(androidx.recyclerview.widget.RecyclerView.EdgeEffectFactory);
+ method public void setHasFixedSize(boolean);
+ method public void setItemAnimator(androidx.recyclerview.widget.RecyclerView.ItemAnimator?);
+ method public void setItemViewCacheSize(int);
+ method @Deprecated public void setLayoutFrozen(boolean);
+ method public void setLayoutManager(androidx.recyclerview.widget.RecyclerView.LayoutManager?);
+ method @Deprecated public void setLayoutTransition(android.animation.LayoutTransition!);
+ method public void setOnFlingListener(androidx.recyclerview.widget.RecyclerView.OnFlingListener?);
+ method @Deprecated public void setOnScrollListener(androidx.recyclerview.widget.RecyclerView.OnScrollListener?);
+ method public void setPreserveFocusAfterLayout(boolean);
+ method public void setRecycledViewPool(androidx.recyclerview.widget.RecyclerView.RecycledViewPool?);
+ method public void setRecyclerListener(androidx.recyclerview.widget.RecyclerView.RecyclerListener?);
+ method public void setScrollingTouchSlop(int);
+ method public void setViewCacheExtension(androidx.recyclerview.widget.RecyclerView.ViewCacheExtension?);
+ method public void smoothScrollBy(@Px int, @Px int);
+ method public void smoothScrollBy(@Px int, @Px int, android.view.animation.Interpolator?);
+ method public void smoothScrollBy(@Px int, @Px int, android.view.animation.Interpolator?, int);
+ method public void smoothScrollToPosition(int);
+ method public boolean startNestedScroll(int, int);
+ method public void stopNestedScroll(int);
+ method public void stopScroll();
+ method public final void suppressLayout(boolean);
+ method public void swapAdapter(androidx.recyclerview.widget.RecyclerView.Adapter?, boolean);
+ field public static final int HORIZONTAL = 0; // 0x0
+ field public static final int INVALID_TYPE = -1; // 0xffffffff
+ field public static final long NO_ID = -1L; // 0xffffffffffffffffL
+ field public static final int NO_POSITION = -1; // 0xffffffff
+ field public static final int SCROLL_STATE_DRAGGING = 1; // 0x1
+ field public static final int SCROLL_STATE_IDLE = 0; // 0x0
+ field public static final int SCROLL_STATE_SETTLING = 2; // 0x2
+ field public static final int TOUCH_SLOP_DEFAULT = 0; // 0x0
+ field public static final int TOUCH_SLOP_PAGING = 1; // 0x1
+ field public static final int UNDEFINED_DURATION = -2147483648; // 0x80000000
+ field public static final int VERTICAL = 1; // 0x1
+ }
+
+ public abstract static class RecyclerView.Adapter<VH extends androidx.recyclerview.widget.RecyclerView.ViewHolder> {
+ ctor public RecyclerView.Adapter();
+ method public final void bindViewHolder(VH, int);
+ method public final VH createViewHolder(android.view.ViewGroup, int);
+ method public abstract int getItemCount();
+ method public long getItemId(int);
+ method public int getItemViewType(int);
+ method public final boolean hasObservers();
+ method public final boolean hasStableIds();
+ method public final void notifyDataSetChanged();
+ method public final void notifyItemChanged(int);
+ method public final void notifyItemChanged(int, Object?);
+ method public final void notifyItemInserted(int);
+ method public final void notifyItemMoved(int, int);
+ method public final void notifyItemRangeChanged(int, int);
+ method public final void notifyItemRangeChanged(int, int, Object?);
+ method public final void notifyItemRangeInserted(int, int);
+ method public final void notifyItemRangeRemoved(int, int);
+ method public final void notifyItemRemoved(int);
+ method public void onAttachedToRecyclerView(androidx.recyclerview.widget.RecyclerView);
+ method public abstract void onBindViewHolder(VH, int);
+ method public void onBindViewHolder(VH, int, java.util.List<java.lang.Object!>);
+ method public abstract VH onCreateViewHolder(android.view.ViewGroup, int);
+ method public void onDetachedFromRecyclerView(androidx.recyclerview.widget.RecyclerView);
+ method public boolean onFailedToRecycleView(VH);
+ method public void onViewAttachedToWindow(VH);
+ method public void onViewDetachedFromWindow(VH);
+ method public void onViewRecycled(VH);
+ method public void registerAdapterDataObserver(androidx.recyclerview.widget.RecyclerView.AdapterDataObserver);
+ method public void setHasStableIds(boolean);
+ method public void unregisterAdapterDataObserver(androidx.recyclerview.widget.RecyclerView.AdapterDataObserver);
+ }
+
+ public abstract static class RecyclerView.AdapterDataObserver {
+ ctor public RecyclerView.AdapterDataObserver();
+ method public void onChanged();
+ method public void onItemRangeChanged(int, int);
+ method public void onItemRangeChanged(int, int, Object?);
+ method public void onItemRangeInserted(int, int);
+ method public void onItemRangeMoved(int, int, int);
+ method public void onItemRangeRemoved(int, int);
+ }
+
+ public static interface RecyclerView.ChildDrawingOrderCallback {
+ method public int onGetChildDrawingOrder(int, int);
+ }
+
+ public static class RecyclerView.EdgeEffectFactory {
+ ctor public RecyclerView.EdgeEffectFactory();
+ method protected android.widget.EdgeEffect createEdgeEffect(androidx.recyclerview.widget.RecyclerView, @androidx.recyclerview.widget.RecyclerView.EdgeEffectFactory.EdgeDirection int);
+ field public static final int DIRECTION_BOTTOM = 3; // 0x3
+ field public static final int DIRECTION_LEFT = 0; // 0x0
+ field public static final int DIRECTION_RIGHT = 2; // 0x2
+ field public static final int DIRECTION_TOP = 1; // 0x1
+ }
+
+ @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE) @IntDef({androidx.recyclerview.widget.RecyclerView.EdgeEffectFactory.DIRECTION_LEFT, androidx.recyclerview.widget.RecyclerView.EdgeEffectFactory.DIRECTION_TOP, androidx.recyclerview.widget.RecyclerView.EdgeEffectFactory.DIRECTION_RIGHT, androidx.recyclerview.widget.RecyclerView.EdgeEffectFactory.DIRECTION_BOTTOM}) public static @interface RecyclerView.EdgeEffectFactory.EdgeDirection {
+ }
+
+ public abstract static class RecyclerView.ItemAnimator {
+ ctor public RecyclerView.ItemAnimator();
+ method public abstract boolean animateAppearance(androidx.recyclerview.widget.RecyclerView.ViewHolder, androidx.recyclerview.widget.RecyclerView.ItemAnimator.ItemHolderInfo?, androidx.recyclerview.widget.RecyclerView.ItemAnimator.ItemHolderInfo);
+ method public abstract boolean animateChange(androidx.recyclerview.widget.RecyclerView.ViewHolder, androidx.recyclerview.widget.RecyclerView.ViewHolder, androidx.recyclerview.widget.RecyclerView.ItemAnimator.ItemHolderInfo, androidx.recyclerview.widget.RecyclerView.ItemAnimator.ItemHolderInfo);
+ method public abstract boolean animateDisappearance(androidx.recyclerview.widget.RecyclerView.ViewHolder, androidx.recyclerview.widget.RecyclerView.ItemAnimator.ItemHolderInfo, androidx.recyclerview.widget.RecyclerView.ItemAnimator.ItemHolderInfo?);
+ method public abstract boolean animatePersistence(androidx.recyclerview.widget.RecyclerView.ViewHolder, androidx.recyclerview.widget.RecyclerView.ItemAnimator.ItemHolderInfo, androidx.recyclerview.widget.RecyclerView.ItemAnimator.ItemHolderInfo);
+ method public boolean canReuseUpdatedViewHolder(androidx.recyclerview.widget.RecyclerView.ViewHolder);
+ method public boolean canReuseUpdatedViewHolder(androidx.recyclerview.widget.RecyclerView.ViewHolder, java.util.List<java.lang.Object!>);
+ method public final void dispatchAnimationFinished(androidx.recyclerview.widget.RecyclerView.ViewHolder);
+ method public final void dispatchAnimationStarted(androidx.recyclerview.widget.RecyclerView.ViewHolder);
+ method public final void dispatchAnimationsFinished();
+ method public abstract void endAnimation(androidx.recyclerview.widget.RecyclerView.ViewHolder);
+ method public abstract void endAnimations();
+ method public long getAddDuration();
+ method public long getChangeDuration();
+ method public long getMoveDuration();
+ method public long getRemoveDuration();
+ method public abstract boolean isRunning();
+ method public final boolean isRunning(androidx.recyclerview.widget.RecyclerView.ItemAnimator.ItemAnimatorFinishedListener?);
+ method public androidx.recyclerview.widget.RecyclerView.ItemAnimator.ItemHolderInfo obtainHolderInfo();
+ method public void onAnimationFinished(androidx.recyclerview.widget.RecyclerView.ViewHolder);
+ method public void onAnimationStarted(androidx.recyclerview.widget.RecyclerView.ViewHolder);
+ method public androidx.recyclerview.widget.RecyclerView.ItemAnimator.ItemHolderInfo recordPostLayoutInformation(androidx.recyclerview.widget.RecyclerView.State, androidx.recyclerview.widget.RecyclerView.ViewHolder);
+ method public androidx.recyclerview.widget.RecyclerView.ItemAnimator.ItemHolderInfo recordPreLayoutInformation(androidx.recyclerview.widget.RecyclerView.State, androidx.recyclerview.widget.RecyclerView.ViewHolder, @androidx.recyclerview.widget.RecyclerView.ItemAnimator.AdapterChanges int, java.util.List<java.lang.Object!>);
+ method public abstract void runPendingAnimations();
+ method public void setAddDuration(long);
+ method public void setChangeDuration(long);
+ method public void setMoveDuration(long);
+ method public void setRemoveDuration(long);
+ field public static final int FLAG_APPEARED_IN_PRE_LAYOUT = 4096; // 0x1000
+ field public static final int FLAG_CHANGED = 2; // 0x2
+ field public static final int FLAG_INVALIDATED = 4; // 0x4
+ field public static final int FLAG_MOVED = 2048; // 0x800
+ field public static final int FLAG_REMOVED = 8; // 0x8
+ }
+
+ @IntDef(flag=true, value={androidx.recyclerview.widget.RecyclerView.ItemAnimator.FLAG_CHANGED, androidx.recyclerview.widget.RecyclerView.ItemAnimator.FLAG_REMOVED, androidx.recyclerview.widget.RecyclerView.ItemAnimator.FLAG_MOVED, androidx.recyclerview.widget.RecyclerView.ItemAnimator.FLAG_INVALIDATED, androidx.recyclerview.widget.RecyclerView.ItemAnimator.FLAG_APPEARED_IN_PRE_LAYOUT}) @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE) public static @interface RecyclerView.ItemAnimator.AdapterChanges {
+ }
+
+ public static interface RecyclerView.ItemAnimator.ItemAnimatorFinishedListener {
+ method public void onAnimationsFinished();
+ }
+
+ public static class RecyclerView.ItemAnimator.ItemHolderInfo {
+ ctor public RecyclerView.ItemAnimator.ItemHolderInfo();
+ method public androidx.recyclerview.widget.RecyclerView.ItemAnimator.ItemHolderInfo setFrom(androidx.recyclerview.widget.RecyclerView.ViewHolder);
+ method public androidx.recyclerview.widget.RecyclerView.ItemAnimator.ItemHolderInfo setFrom(androidx.recyclerview.widget.RecyclerView.ViewHolder, @androidx.recyclerview.widget.RecyclerView.ItemAnimator.AdapterChanges int);
+ field public int bottom;
+ field @androidx.recyclerview.widget.RecyclerView.ItemAnimator.AdapterChanges public int changeFlags;
+ field public int left;
+ field public int right;
+ field public int top;
+ }
+
+ public abstract static class RecyclerView.ItemDecoration {
+ ctor public RecyclerView.ItemDecoration();
+ method @Deprecated public void getItemOffsets(android.graphics.Rect, int, androidx.recyclerview.widget.RecyclerView);
+ method public void getItemOffsets(android.graphics.Rect, android.view.View, androidx.recyclerview.widget.RecyclerView, androidx.recyclerview.widget.RecyclerView.State);
+ method public void onDraw(android.graphics.Canvas, androidx.recyclerview.widget.RecyclerView, androidx.recyclerview.widget.RecyclerView.State);
+ method @Deprecated public void onDraw(android.graphics.Canvas, androidx.recyclerview.widget.RecyclerView);
+ method public void onDrawOver(android.graphics.Canvas, androidx.recyclerview.widget.RecyclerView, androidx.recyclerview.widget.RecyclerView.State);
+ method @Deprecated public void onDrawOver(android.graphics.Canvas, androidx.recyclerview.widget.RecyclerView);
+ }
+
+ public abstract static class RecyclerView.LayoutManager {
+ ctor public RecyclerView.LayoutManager();
+ method public void addDisappearingView(android.view.View!);
+ method public void addDisappearingView(android.view.View!, int);
+ method public void addView(android.view.View!);
+ method public void addView(android.view.View!, int);
+ method public void assertInLayoutOrScroll(String!);
+ method public void assertNotInLayoutOrScroll(String!);
+ method public void attachView(android.view.View, int, androidx.recyclerview.widget.RecyclerView.LayoutParams!);
+ method public void attachView(android.view.View, int);
+ method public void attachView(android.view.View);
+ method public void calculateItemDecorationsForChild(android.view.View, android.graphics.Rect);
+ method public boolean canScrollHorizontally();
+ method public boolean canScrollVertically();
+ method public boolean checkLayoutParams(androidx.recyclerview.widget.RecyclerView.LayoutParams!);
+ method public static int chooseSize(int, int, int);
+ method public void collectAdjacentPrefetchPositions(int, int, androidx.recyclerview.widget.RecyclerView.State!, androidx.recyclerview.widget.RecyclerView.LayoutManager.LayoutPrefetchRegistry!);
+ method public void collectInitialPrefetchPositions(int, androidx.recyclerview.widget.RecyclerView.LayoutManager.LayoutPrefetchRegistry!);
+ method public int computeHorizontalScrollExtent(androidx.recyclerview.widget.RecyclerView.State);
+ method public int computeHorizontalScrollOffset(androidx.recyclerview.widget.RecyclerView.State);
+ method public int computeHorizontalScrollRange(androidx.recyclerview.widget.RecyclerView.State);
+ method public int computeVerticalScrollExtent(androidx.recyclerview.widget.RecyclerView.State);
+ method public int computeVerticalScrollOffset(androidx.recyclerview.widget.RecyclerView.State);
+ method public int computeVerticalScrollRange(androidx.recyclerview.widget.RecyclerView.State);
+ method public void detachAndScrapAttachedViews(androidx.recyclerview.widget.RecyclerView.Recycler);
+ method public void detachAndScrapView(android.view.View, androidx.recyclerview.widget.RecyclerView.Recycler);
+ method public void detachAndScrapViewAt(int, androidx.recyclerview.widget.RecyclerView.Recycler);
+ method public void detachView(android.view.View);
+ method public void detachViewAt(int);
+ method public void endAnimation(android.view.View!);
+ method public android.view.View? findContainingItemView(android.view.View);
+ method public android.view.View? findViewByPosition(int);
+ method public abstract androidx.recyclerview.widget.RecyclerView.LayoutParams! generateDefaultLayoutParams();
+ method public androidx.recyclerview.widget.RecyclerView.LayoutParams! generateLayoutParams(android.view.ViewGroup.LayoutParams!);
+ method public androidx.recyclerview.widget.RecyclerView.LayoutParams! generateLayoutParams(android.content.Context!, android.util.AttributeSet!);
+ method public int getBaseline();
+ method public int getBottomDecorationHeight(android.view.View);
+ method public android.view.View? getChildAt(int);
+ method public int getChildCount();
+ method @Deprecated public static int getChildMeasureSpec(int, int, int, boolean);
+ method public static int getChildMeasureSpec(int, int, int, int, boolean);
+ method public boolean getClipToPadding();
+ method public int getColumnCountForAccessibility(androidx.recyclerview.widget.RecyclerView.Recycler, androidx.recyclerview.widget.RecyclerView.State);
+ method public int getDecoratedBottom(android.view.View);
+ method public void getDecoratedBoundsWithMargins(android.view.View, android.graphics.Rect);
+ method public int getDecoratedLeft(android.view.View);
+ method public int getDecoratedMeasuredHeight(android.view.View);
+ method public int getDecoratedMeasuredWidth(android.view.View);
+ method public int getDecoratedRight(android.view.View);
+ method public int getDecoratedTop(android.view.View);
+ method public android.view.View? getFocusedChild();
+ method @Px public int getHeight();
+ method public int getHeightMode();
+ method public int getItemCount();
+ method public int getItemViewType(android.view.View);
+ method public int getLayoutDirection();
+ method public int getLeftDecorationWidth(android.view.View);
+ method @Px public int getMinimumHeight();
+ method @Px public int getMinimumWidth();
+ method @Px public int getPaddingBottom();
+ method @Px public int getPaddingEnd();
+ method @Px public int getPaddingLeft();
+ method @Px public int getPaddingRight();
+ method @Px public int getPaddingStart();
+ method @Px public int getPaddingTop();
+ method public int getPosition(android.view.View);
+ method public static androidx.recyclerview.widget.RecyclerView.LayoutManager.Properties! getProperties(android.content.Context, android.util.AttributeSet?, int, int);
+ method public int getRightDecorationWidth(android.view.View);
+ method public int getRowCountForAccessibility(androidx.recyclerview.widget.RecyclerView.Recycler, androidx.recyclerview.widget.RecyclerView.State);
+ method public int getSelectionModeForAccessibility(androidx.recyclerview.widget.RecyclerView.Recycler, androidx.recyclerview.widget.RecyclerView.State);
+ method public int getTopDecorationHeight(android.view.View);
+ method public void getTransformedBoundingBox(android.view.View, boolean, android.graphics.Rect);
+ method @Px public int getWidth();
+ method public int getWidthMode();
+ method public boolean hasFocus();
+ method public void ignoreView(android.view.View);
+ method public boolean isAttachedToWindow();
+ method public boolean isAutoMeasureEnabled();
+ method public boolean isFocused();
+ method public final boolean isItemPrefetchEnabled();
+ method public boolean isLayoutHierarchical(androidx.recyclerview.widget.RecyclerView.Recycler, androidx.recyclerview.widget.RecyclerView.State);
+ method public boolean isMeasurementCacheEnabled();
+ method public boolean isSmoothScrolling();
+ method public boolean isViewPartiallyVisible(android.view.View, boolean, boolean);
+ method public void layoutDecorated(android.view.View, int, int, int, int);
+ method public void layoutDecoratedWithMargins(android.view.View, int, int, int, int);
+ method public void measureChild(android.view.View, int, int);
+ method public void measureChildWithMargins(android.view.View, int, int);
+ method public void moveView(int, int);
+ method public void offsetChildrenHorizontal(@Px int);
+ method public void offsetChildrenVertical(@Px int);
+ method public void onAdapterChanged(androidx.recyclerview.widget.RecyclerView.Adapter?, androidx.recyclerview.widget.RecyclerView.Adapter?);
+ method public boolean onAddFocusables(androidx.recyclerview.widget.RecyclerView, java.util.ArrayList<android.view.View!>, int, int);
+ method @CallSuper public void onAttachedToWindow(androidx.recyclerview.widget.RecyclerView!);
+ method @Deprecated public void onDetachedFromWindow(androidx.recyclerview.widget.RecyclerView!);
+ method @CallSuper public void onDetachedFromWindow(androidx.recyclerview.widget.RecyclerView!, androidx.recyclerview.widget.RecyclerView.Recycler!);
+ method public android.view.View? onFocusSearchFailed(android.view.View, int, androidx.recyclerview.widget.RecyclerView.Recycler, androidx.recyclerview.widget.RecyclerView.State);
+ method public void onInitializeAccessibilityEvent(android.view.accessibility.AccessibilityEvent);
+ method public void onInitializeAccessibilityEvent(androidx.recyclerview.widget.RecyclerView.Recycler, androidx.recyclerview.widget.RecyclerView.State, android.view.accessibility.AccessibilityEvent);
+ method public void onInitializeAccessibilityNodeInfo(androidx.recyclerview.widget.RecyclerView.Recycler, androidx.recyclerview.widget.RecyclerView.State, androidx.core.view.accessibility.AccessibilityNodeInfoCompat);
+ method public void onInitializeAccessibilityNodeInfoForItem(androidx.recyclerview.widget.RecyclerView.Recycler, androidx.recyclerview.widget.RecyclerView.State, android.view.View, androidx.core.view.accessibility.AccessibilityNodeInfoCompat);
+ method public android.view.View? onInterceptFocusSearch(android.view.View, int);
+ method public void onItemsAdded(androidx.recyclerview.widget.RecyclerView, int, int);
+ method public void onItemsChanged(androidx.recyclerview.widget.RecyclerView);
+ method public void onItemsMoved(androidx.recyclerview.widget.RecyclerView, int, int, int);
+ method public void onItemsRemoved(androidx.recyclerview.widget.RecyclerView, int, int);
+ method public void onItemsUpdated(androidx.recyclerview.widget.RecyclerView, int, int);
+ method public void onItemsUpdated(androidx.recyclerview.widget.RecyclerView, int, int, Object?);
+ method public void onLayoutChildren(androidx.recyclerview.widget.RecyclerView.Recycler!, androidx.recyclerview.widget.RecyclerView.State!);
+ method public void onLayoutCompleted(androidx.recyclerview.widget.RecyclerView.State!);
+ method public void onMeasure(androidx.recyclerview.widget.RecyclerView.Recycler, androidx.recyclerview.widget.RecyclerView.State, int, int);
+ method @Deprecated public boolean onRequestChildFocus(androidx.recyclerview.widget.RecyclerView, android.view.View, android.view.View?);
+ method public boolean onRequestChildFocus(androidx.recyclerview.widget.RecyclerView, androidx.recyclerview.widget.RecyclerView.State, android.view.View, android.view.View?);
+ method public void onRestoreInstanceState(android.os.Parcelable!);
+ method public android.os.Parcelable? onSaveInstanceState();
+ method public void onScrollStateChanged(int);
+ method public boolean performAccessibilityAction(androidx.recyclerview.widget.RecyclerView.Recycler, androidx.recyclerview.widget.RecyclerView.State, int, android.os.Bundle?);
+ method public boolean performAccessibilityActionForItem(androidx.recyclerview.widget.RecyclerView.Recycler, androidx.recyclerview.widget.RecyclerView.State, android.view.View, int, android.os.Bundle?);
+ method public void postOnAnimation(Runnable!);
+ method public void removeAllViews();
+ method public void removeAndRecycleAllViews(androidx.recyclerview.widget.RecyclerView.Recycler);
+ method public void removeAndRecycleView(android.view.View, androidx.recyclerview.widget.RecyclerView.Recycler);
+ method public void removeAndRecycleViewAt(int, androidx.recyclerview.widget.RecyclerView.Recycler);
+ method public boolean removeCallbacks(Runnable!);
+ method public void removeDetachedView(android.view.View);
+ method public void removeView(android.view.View!);
+ method public void removeViewAt(int);
+ method public boolean requestChildRectangleOnScreen(androidx.recyclerview.widget.RecyclerView, android.view.View, android.graphics.Rect, boolean);
+ method public boolean requestChildRectangleOnScreen(androidx.recyclerview.widget.RecyclerView, android.view.View, android.graphics.Rect, boolean, boolean);
+ method public void requestLayout();
+ method public void requestSimpleAnimationsInNextLayout();
+ method public int scrollHorizontallyBy(int, androidx.recyclerview.widget.RecyclerView.Recycler!, androidx.recyclerview.widget.RecyclerView.State!);
+ method public void scrollToPosition(int);
+ method public int scrollVerticallyBy(int, androidx.recyclerview.widget.RecyclerView.Recycler!, androidx.recyclerview.widget.RecyclerView.State!);
+ method @Deprecated public void setAutoMeasureEnabled(boolean);
+ method public final void setItemPrefetchEnabled(boolean);
+ method public void setMeasuredDimension(android.graphics.Rect!, int, int);
+ method public void setMeasuredDimension(int, int);
+ method public void setMeasurementCacheEnabled(boolean);
+ method public void smoothScrollToPosition(androidx.recyclerview.widget.RecyclerView!, androidx.recyclerview.widget.RecyclerView.State!, int);
+ method public void startSmoothScroll(androidx.recyclerview.widget.RecyclerView.SmoothScroller!);
+ method public void stopIgnoringView(android.view.View);
+ method public boolean supportsPredictiveItemAnimations();
+ }
+
+ public static interface RecyclerView.LayoutManager.LayoutPrefetchRegistry {
+ method public void addPosition(int, int);
+ }
+
+ public static class RecyclerView.LayoutManager.Properties {
+ ctor public RecyclerView.LayoutManager.Properties();
+ field public int orientation;
+ field public boolean reverseLayout;
+ field public int spanCount;
+ field public boolean stackFromEnd;
+ }
+
+ public static class RecyclerView.LayoutParams extends android.view.ViewGroup.MarginLayoutParams {
+ ctor public RecyclerView.LayoutParams(android.content.Context!, android.util.AttributeSet!);
+ ctor public RecyclerView.LayoutParams(int, int);
+ ctor public RecyclerView.LayoutParams(android.view.ViewGroup.MarginLayoutParams!);
+ ctor public RecyclerView.LayoutParams(android.view.ViewGroup.LayoutParams!);
+ ctor public RecyclerView.LayoutParams(androidx.recyclerview.widget.RecyclerView.LayoutParams!);
+ method public int getViewAdapterPosition();
+ method public int getViewLayoutPosition();
+ method @Deprecated public int getViewPosition();
+ method public boolean isItemChanged();
+ method public boolean isItemRemoved();
+ method public boolean isViewInvalid();
+ method public boolean viewNeedsUpdate();
+ }
+
+ public static interface RecyclerView.OnChildAttachStateChangeListener {
+ method public void onChildViewAttachedToWindow(android.view.View);
+ method public void onChildViewDetachedFromWindow(android.view.View);
+ }
+
+ public abstract static class RecyclerView.OnFlingListener {
+ ctor public RecyclerView.OnFlingListener();
+ method public abstract boolean onFling(int, int);
+ }
+
+ public static interface RecyclerView.OnItemTouchListener {
+ method public boolean onInterceptTouchEvent(androidx.recyclerview.widget.RecyclerView, android.view.MotionEvent);
+ method public void onRequestDisallowInterceptTouchEvent(boolean);
+ method public void onTouchEvent(androidx.recyclerview.widget.RecyclerView, android.view.MotionEvent);
+ }
+
+ public abstract static class RecyclerView.OnScrollListener {
+ ctor public RecyclerView.OnScrollListener();
+ method public void onScrollStateChanged(androidx.recyclerview.widget.RecyclerView, int);
+ method public void onScrolled(androidx.recyclerview.widget.RecyclerView, int, int);
+ }
+
+ @IntDef({androidx.recyclerview.widget.RecyclerView.HORIZONTAL, androidx.recyclerview.widget.RecyclerView.VERTICAL}) @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE) public static @interface RecyclerView.Orientation {
+ }
+
+ public static class RecyclerView.RecycledViewPool {
+ ctor public RecyclerView.RecycledViewPool();
+ method public void clear();
+ method public androidx.recyclerview.widget.RecyclerView.ViewHolder? getRecycledView(int);
+ method public int getRecycledViewCount(int);
+ method public void putRecycledView(androidx.recyclerview.widget.RecyclerView.ViewHolder!);
+ method public void setMaxRecycledViews(int, int);
+ }
+
+ public final class RecyclerView.Recycler {
+ ctor public RecyclerView.Recycler();
+ method public void bindViewToPosition(android.view.View, int);
+ method public void clear();
+ method public int convertPreLayoutPositionToPostLayout(int);
+ method public java.util.List<androidx.recyclerview.widget.RecyclerView.ViewHolder!> getScrapList();
+ method public android.view.View getViewForPosition(int);
+ method public void recycleView(android.view.View);
+ method public void setViewCacheSize(int);
+ }
+
+ public static interface RecyclerView.RecyclerListener {
+ method public void onViewRecycled(androidx.recyclerview.widget.RecyclerView.ViewHolder);
+ }
+
+
+ public static class RecyclerView.SimpleOnItemTouchListener implements androidx.recyclerview.widget.RecyclerView.OnItemTouchListener {
+ ctor public RecyclerView.SimpleOnItemTouchListener();
+ method public boolean onInterceptTouchEvent(androidx.recyclerview.widget.RecyclerView, android.view.MotionEvent);
+ method public void onRequestDisallowInterceptTouchEvent(boolean);
+ method public void onTouchEvent(androidx.recyclerview.widget.RecyclerView, android.view.MotionEvent);
+ }
+
+ public abstract static class RecyclerView.SmoothScroller {
+ ctor public RecyclerView.SmoothScroller();
+ method public android.graphics.PointF? computeScrollVectorForPosition(int);
+ method public android.view.View! findViewByPosition(int);
+ method public int getChildCount();
+ method public int getChildPosition(android.view.View!);
+ method public androidx.recyclerview.widget.RecyclerView.LayoutManager? getLayoutManager();
+ method public int getTargetPosition();
+ method @Deprecated public void instantScrollToPosition(int);
+ method public boolean isPendingInitialRun();
+ method public boolean isRunning();
+ method protected void normalize(android.graphics.PointF);
+ method protected void onChildAttachedToWindow(android.view.View!);
+ method protected abstract void onSeekTargetStep(@Px int, @Px int, androidx.recyclerview.widget.RecyclerView.State, androidx.recyclerview.widget.RecyclerView.SmoothScroller.Action);
+ method protected abstract void onStart();
+ method protected abstract void onStop();
+ method protected abstract void onTargetFound(android.view.View, androidx.recyclerview.widget.RecyclerView.State, androidx.recyclerview.widget.RecyclerView.SmoothScroller.Action);
+ method public void setTargetPosition(int);
+ method protected final void stop();
+ }
+
+ public static class RecyclerView.SmoothScroller.Action {
+ ctor public RecyclerView.SmoothScroller.Action(@Px int, @Px int);
+ ctor public RecyclerView.SmoothScroller.Action(@Px int, @Px int, int);
+ ctor public RecyclerView.SmoothScroller.Action(@Px int, @Px int, int, android.view.animation.Interpolator?);
+ method public int getDuration();
+ method @Px public int getDx();
+ method @Px public int getDy();
+ method public android.view.animation.Interpolator? getInterpolator();
+ method public void jumpTo(int);
+ method public void setDuration(int);
+ method public void setDx(@Px int);
+ method public void setDy(@Px int);
+ method public void setInterpolator(android.view.animation.Interpolator?);
+ method public void update(@Px int, @Px int, int, android.view.animation.Interpolator?);
+ field public static final int UNDEFINED_DURATION = -2147483648; // 0x80000000
+ }
+
+ public static interface RecyclerView.SmoothScroller.ScrollVectorProvider {
+ method public android.graphics.PointF? computeScrollVectorForPosition(int);
+ }
+
+ public static class RecyclerView.State {
+ ctor public RecyclerView.State();
+ method public boolean didStructureChange();
+ method public <T> T! get(int);
+ method public int getItemCount();
+ method public int getRemainingScrollHorizontal();
+ method public int getRemainingScrollVertical();
+ method public int getTargetScrollPosition();
+ method public boolean hasTargetScrollPosition();
+ method public boolean isMeasuring();
+ method public boolean isPreLayout();
+ method public void put(int, Object!);
+ method public void remove(int);
+ method public boolean willRunPredictiveAnimations();
+ method public boolean willRunSimpleAnimations();
+ }
+
+ public abstract static class RecyclerView.ViewCacheExtension {
+ ctor public RecyclerView.ViewCacheExtension();
+ method public abstract android.view.View? getViewForPositionAndType(androidx.recyclerview.widget.RecyclerView.Recycler, int, int);
+ }
+
+ public abstract static class RecyclerView.ViewHolder {
+ ctor public RecyclerView.ViewHolder(android.view.View);
+ method public final int getAdapterPosition();
+ method public final long getItemId();
+ method public final int getItemViewType();
+ method public final int getLayoutPosition();
+ method public final int getOldPosition();
+ method @Deprecated public final int getPosition();
+ method public final boolean isRecyclable();
+ method public final void setIsRecyclable(boolean);
+ field public final android.view.View itemView;
+ }
+
+ public class RecyclerViewAccessibilityDelegate extends androidx.core.view.AccessibilityDelegateCompat {
+ ctor public RecyclerViewAccessibilityDelegate(androidx.recyclerview.widget.RecyclerView);
+ method public androidx.core.view.AccessibilityDelegateCompat getItemDelegate();
+ }
+
+ public static class RecyclerViewAccessibilityDelegate.ItemDelegate extends androidx.core.view.AccessibilityDelegateCompat {
+ ctor public RecyclerViewAccessibilityDelegate.ItemDelegate(androidx.recyclerview.widget.RecyclerViewAccessibilityDelegate);
+ }
+
+ public abstract class SimpleItemAnimator extends androidx.recyclerview.widget.RecyclerView.ItemAnimator {
+ ctor public SimpleItemAnimator();
+ method public abstract boolean animateAdd(androidx.recyclerview.widget.RecyclerView.ViewHolder!);
+ method public boolean animateAppearance(androidx.recyclerview.widget.RecyclerView.ViewHolder, androidx.recyclerview.widget.RecyclerView.ItemAnimator.ItemHolderInfo?, androidx.recyclerview.widget.RecyclerView.ItemAnimator.ItemHolderInfo);
+ method public boolean animateChange(androidx.recyclerview.widget.RecyclerView.ViewHolder, androidx.recyclerview.widget.RecyclerView.ViewHolder, androidx.recyclerview.widget.RecyclerView.ItemAnimator.ItemHolderInfo, androidx.recyclerview.widget.RecyclerView.ItemAnimator.ItemHolderInfo);
+ method public abstract boolean animateChange(androidx.recyclerview.widget.RecyclerView.ViewHolder!, androidx.recyclerview.widget.RecyclerView.ViewHolder!, int, int, int, int);
+ method public boolean animateDisappearance(androidx.recyclerview.widget.RecyclerView.ViewHolder, androidx.recyclerview.widget.RecyclerView.ItemAnimator.ItemHolderInfo, androidx.recyclerview.widget.RecyclerView.ItemAnimator.ItemHolderInfo?);
+ method public abstract boolean animateMove(androidx.recyclerview.widget.RecyclerView.ViewHolder!, int, int, int, int);
+ method public boolean animatePersistence(androidx.recyclerview.widget.RecyclerView.ViewHolder, androidx.recyclerview.widget.RecyclerView.ItemAnimator.ItemHolderInfo, androidx.recyclerview.widget.RecyclerView.ItemAnimator.ItemHolderInfo);
+ method public abstract boolean animateRemove(androidx.recyclerview.widget.RecyclerView.ViewHolder!);
+ method public final void dispatchAddFinished(androidx.recyclerview.widget.RecyclerView.ViewHolder!);
+ method public final void dispatchAddStarting(androidx.recyclerview.widget.RecyclerView.ViewHolder!);
+ method public final void dispatchChangeFinished(androidx.recyclerview.widget.RecyclerView.ViewHolder!, boolean);
+ method public final void dispatchChangeStarting(androidx.recyclerview.widget.RecyclerView.ViewHolder!, boolean);
+ method public final void dispatchMoveFinished(androidx.recyclerview.widget.RecyclerView.ViewHolder!);
+ method public final void dispatchMoveStarting(androidx.recyclerview.widget.RecyclerView.ViewHolder!);
+ method public final void dispatchRemoveFinished(androidx.recyclerview.widget.RecyclerView.ViewHolder!);
+ method public final void dispatchRemoveStarting(androidx.recyclerview.widget.RecyclerView.ViewHolder!);
+ method public boolean getSupportsChangeAnimations();
+ method public void onAddFinished(androidx.recyclerview.widget.RecyclerView.ViewHolder!);
+ method public void onAddStarting(androidx.recyclerview.widget.RecyclerView.ViewHolder!);
+ method public void onChangeFinished(androidx.recyclerview.widget.RecyclerView.ViewHolder!, boolean);
+ method public void onChangeStarting(androidx.recyclerview.widget.RecyclerView.ViewHolder!, boolean);
+ method public void onMoveFinished(androidx.recyclerview.widget.RecyclerView.ViewHolder!);
+ method public void onMoveStarting(androidx.recyclerview.widget.RecyclerView.ViewHolder!);
+ method public void onRemoveFinished(androidx.recyclerview.widget.RecyclerView.ViewHolder!);
+ method public void onRemoveStarting(androidx.recyclerview.widget.RecyclerView.ViewHolder!);
+ method public void setSupportsChangeAnimations(boolean);
+ }
+
+ public abstract class SnapHelper extends androidx.recyclerview.widget.RecyclerView.OnFlingListener {
+ ctor public SnapHelper();
+ method public void attachToRecyclerView(androidx.recyclerview.widget.RecyclerView?) throws java.lang.IllegalStateException;
+ method public abstract int[]? calculateDistanceToFinalSnap(androidx.recyclerview.widget.RecyclerView.LayoutManager, android.view.View);
+ method public int[]! calculateScrollDistance(int, int);
+ method protected androidx.recyclerview.widget.RecyclerView.SmoothScroller? createScroller(androidx.recyclerview.widget.RecyclerView.LayoutManager!);
+ method @Deprecated protected androidx.recyclerview.widget.LinearSmoothScroller? createSnapScroller(androidx.recyclerview.widget.RecyclerView.LayoutManager!);
+ method public abstract android.view.View? findSnapView(androidx.recyclerview.widget.RecyclerView.LayoutManager!);
+ method public abstract int findTargetSnapPosition(androidx.recyclerview.widget.RecyclerView.LayoutManager!, int, int);
+ method public boolean onFling(int, int);
+ }
+
+ public class SortedList<T> {
+ ctor public SortedList(Class<T!>, androidx.recyclerview.widget.SortedList.Callback<T!>);
+ ctor public SortedList(Class<T!>, androidx.recyclerview.widget.SortedList.Callback<T!>, int);
+ method public int add(T!);
+ method public void addAll(T![], boolean);
+ method public void addAll(T!...);
+ method public void addAll(java.util.Collection<T!>);
+ method public void beginBatchedUpdates();
+ method public void clear();
+ method public void endBatchedUpdates();
+ method public T! get(int) throws java.lang.IndexOutOfBoundsException;
+ method public int indexOf(T!);
+ method public void recalculatePositionOfItemAt(int);
+ method public boolean remove(T!);
+ method public T! removeItemAt(int);
+ method public void replaceAll(T![], boolean);
+ method public void replaceAll(T!...);
+ method public void replaceAll(java.util.Collection<T!>);
+ method public int size();
+ method public void updateItemAt(int, T!);
+ field public static final int INVALID_POSITION = -1; // 0xffffffff
+ }
+
+ public static class SortedList.BatchedCallback<T2> extends androidx.recyclerview.widget.SortedList.Callback<T2> {
+ ctor public SortedList.BatchedCallback(androidx.recyclerview.widget.SortedList.Callback<T2!>!);
+ method public boolean areContentsTheSame(T2!, T2!);
+ method public boolean areItemsTheSame(T2!, T2!);
+ method public int compare(T2!, T2!);
+ method public void dispatchLastEvent();
+ method public void onChanged(int, int);
+ method public void onInserted(int, int);
+ method public void onMoved(int, int);
+ method public void onRemoved(int, int);
+ }
+
+ public abstract static class SortedList.Callback<T2> implements java.util.Comparator<T2> androidx.recyclerview.widget.ListUpdateCallback {
+ ctor public SortedList.Callback();
+ method public abstract boolean areContentsTheSame(T2!, T2!);
+ method public abstract boolean areItemsTheSame(T2!, T2!);
+ method public abstract int compare(T2!, T2!);
+ method public Object? getChangePayload(T2!, T2!);
+ method public abstract void onChanged(int, int);
+ method public void onChanged(int, int, Object!);
+ }
+
+ public abstract class SortedListAdapterCallback<T2> extends androidx.recyclerview.widget.SortedList.Callback<T2> {
+ ctor public SortedListAdapterCallback(androidx.recyclerview.widget.RecyclerView.Adapter!);
+ method public void onChanged(int, int);
+ method public void onInserted(int, int);
+ method public void onMoved(int, int);
+ method public void onRemoved(int, int);
+ }
+
+ public class StaggeredGridLayoutManager extends androidx.recyclerview.widget.RecyclerView.LayoutManager implements androidx.recyclerview.widget.RecyclerView.SmoothScroller.ScrollVectorProvider {
+ ctor public StaggeredGridLayoutManager(android.content.Context!, android.util.AttributeSet!, int, int);
+ ctor public StaggeredGridLayoutManager(int, int);
+ method public android.graphics.PointF! computeScrollVectorForPosition(int);
+ method public int[]! findFirstCompletelyVisibleItemPositions(int[]!);
+ method public int[]! findFirstVisibleItemPositions(int[]!);
+ method public int[]! findLastCompletelyVisibleItemPositions(int[]!);
+ method public int[]! findLastVisibleItemPositions(int[]!);
+ method public androidx.recyclerview.widget.RecyclerView.LayoutParams! generateDefaultLayoutParams();
+ method public int getGapStrategy();
+ method public int getOrientation();
+ method public boolean getReverseLayout();
+ method public int getSpanCount();
+ method public void invalidateSpanAssignments();
+ method public void scrollToPositionWithOffset(int, int);
+ method public void setGapStrategy(int);
+ method public void setOrientation(int);
+ method public void setReverseLayout(boolean);
+ method public void setSpanCount(int);
+ field @Deprecated public static final int GAP_HANDLING_LAZY = 1; // 0x1
+ field public static final int GAP_HANDLING_MOVE_ITEMS_BETWEEN_SPANS = 2; // 0x2
+ field public static final int GAP_HANDLING_NONE = 0; // 0x0
+ field public static final int HORIZONTAL = 0; // 0x0
+ field public static final int VERTICAL = 1; // 0x1
+ }
+
+ public static class StaggeredGridLayoutManager.LayoutParams extends androidx.recyclerview.widget.RecyclerView.LayoutParams {
+ ctor public StaggeredGridLayoutManager.LayoutParams(android.content.Context!, android.util.AttributeSet!);
+ ctor public StaggeredGridLayoutManager.LayoutParams(int, int);
+ ctor public StaggeredGridLayoutManager.LayoutParams(android.view.ViewGroup.MarginLayoutParams!);
+ ctor public StaggeredGridLayoutManager.LayoutParams(android.view.ViewGroup.LayoutParams!);
+ ctor public StaggeredGridLayoutManager.LayoutParams(androidx.recyclerview.widget.RecyclerView.LayoutParams!);
+ method public final int getSpanIndex();
+ method public boolean isFullSpan();
+ method public void setFullSpan(boolean);
+ field public static final int INVALID_SPAN_ID = -1; // 0xffffffff
+ }
+
+
+}
+
diff --git a/recyclerview/recyclerview/build.gradle b/recyclerview/recyclerview/build.gradle
index 16d7734..4ec3faf 100644
--- a/recyclerview/recyclerview/build.gradle
+++ b/recyclerview/recyclerview/build.gradle
@@ -11,7 +11,7 @@
dependencies {
api("androidx.annotation:annotation:1.1.0")
- api("androidx.core:core:1.1.0-rc02")
+ api("androidx.core:core:1.1.0")
implementation("androidx.collection:collection:1.0.0")
api("androidx.customview:customview:1.0.0")
diff --git a/recyclerview/recyclerview/src/androidTest/java/androidx/recyclerview/widget/LinearLayoutManagerExtraLayoutSpaceTest.java b/recyclerview/recyclerview/src/androidTest/java/androidx/recyclerview/widget/LinearLayoutManagerExtraLayoutSpaceTest.java
index 70b1225..8f87cf5 100644
--- a/recyclerview/recyclerview/src/androidTest/java/androidx/recyclerview/widget/LinearLayoutManagerExtraLayoutSpaceTest.java
+++ b/recyclerview/recyclerview/src/androidTest/java/androidx/recyclerview/widget/LinearLayoutManagerExtraLayoutSpaceTest.java
@@ -69,6 +69,7 @@
private int mCurrPosition = 0;
private ScrollDirection mLastScrollDirection = TOWARDS_END;
+ private LastScrollDeltaTracker mLastScrollTracker = new LastScrollDeltaTracker();
public LinearLayoutManagerExtraLayoutSpaceTest(Config config, int extraLayoutSpaceLegacy,
int extraLayoutSpace) {
@@ -116,6 +117,7 @@
mLayoutManager = (ExtraLayoutSpaceLayoutManager) super.mLayoutManager;
mLayoutManager.mExtraLayoutSpaceLegacy = mExtraLayoutSpaceLegacy;
mLayoutManager.mExtraLayoutSpace = mExtraLayoutSpace;
+ mRecyclerView.addOnScrollListener(mLastScrollTracker);
// Verify start position
verifyStartPosition();
@@ -142,6 +144,13 @@
// Perform the scroll
scrollToPosition(mCurrPosition, smoothScroll);
+ int direction = Integer.signum(mCurrPosition - prevPosition);
+ if (smoothScroll) {
+ // TODO(b/139350295): fix the overshoot instead of detecting it
+ while (!isLastScrollDirectionCorrect(direction)) {
+ correctLastScrollDirection();
+ }
+ }
// Update expected results
// Alignment means the side of the viewport to which mCurrPosition is aligned
@@ -155,6 +164,25 @@
verify(getExpectedExtraSpace(smoothScroll), getAvailableSpace(alignment));
}
+ private boolean isLastScrollDirectionCorrect(int expectedDirection) {
+ int lastDirection = mLastScrollTracker.get(mConfig.mOrientation);
+ int reversedModifier = isReversed() ? -1 : 1;
+ return lastDirection * reversedModifier * expectedDirection >= 0;
+ }
+
+ private void correctLastScrollDirection() throws Throwable {
+ final int dx = Integer.signum(mLastScrollTracker.getX());
+ final int dy = Integer.signum(mLastScrollTracker.getY());
+
+ mLayoutManager.expectIdleState(1);
+ mRecyclerView.smoothScrollBy(dx, dy);
+ mLayoutManager.waitForSnap(10);
+
+ mLayoutManager.expectIdleState(1);
+ mRecyclerView.smoothScrollBy(-dx, -dy);
+ mLayoutManager.waitForSnap(10);
+ }
+
private void scrollToPosition(final int position, final boolean smoothScroll) throws Throwable {
mActivityRule.runOnUiThread(new Runnable() {
@Override
@@ -269,6 +297,29 @@
);
}
+
+ private class LastScrollDeltaTracker extends RecyclerView.OnScrollListener {
+ public final int[] mLastScrollDelta = new int[2];
+
+ @Override
+ public void onScrolled(@NonNull RecyclerView recyclerView, int dx, int dy) {
+ mLastScrollDelta[0] = dx;
+ mLastScrollDelta[1] = dy;
+ }
+
+ public int getX() {
+ return mLastScrollDelta[0];
+ }
+
+ public int getY() {
+ return mLastScrollDelta[1];
+ }
+
+ public int get(int orientation) {
+ return mLastScrollDelta[orientation];
+ }
+ }
+
class ExtraLayoutSpaceLayoutManager extends WrappedLinearLayoutManager {
int mExtraLayoutSpaceLegacy = -1;
int[] mExtraLayoutSpace = null;
diff --git a/room/common/api/2.2.0-beta01.ignore b/room/common/api/2.2.0-beta01.ignore
new file mode 100644
index 0000000..51b51d5
--- /dev/null
+++ b/room/common/api/2.2.0-beta01.ignore
@@ -0,0 +1,13 @@
+// Baseline format: 1.0
+ChangedType: androidx.room.Database#entities():
+ Method androidx.room.Database.entities has changed return type from Class[] to Class<?>[]
+ChangedType: androidx.room.Database#views():
+ Method androidx.room.Database.views has changed return type from Class[] to Class<?>[]
+ChangedType: androidx.room.ForeignKey#entity():
+ Method androidx.room.ForeignKey.entity has changed return type from Class to Class<?>
+ChangedType: androidx.room.Fts4#contentEntity():
+ Method androidx.room.Fts4.contentEntity has changed return type from Class to Class<?>
+ChangedType: androidx.room.RawQuery#observedEntities():
+ Method androidx.room.RawQuery.observedEntities has changed return type from Class[] to Class<?>[]
+ChangedType: androidx.room.Relation#entity():
+ Method androidx.room.Relation.entity has changed return type from Class to Class<?>
diff --git a/room/common/api/2.2.0-beta01.txt b/room/common/api/2.2.0-beta01.txt
new file mode 100644
index 0000000..238967d
--- /dev/null
+++ b/room/common/api/2.2.0-beta01.txt
@@ -0,0 +1,197 @@
+// Signature format: 3.0
+package androidx.room {
+
+ @java.lang.annotation.Target({java.lang.annotation.ElementType.FIELD, java.lang.annotation.ElementType.METHOD}) @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) public @interface ColumnInfo {
+ method @androidx.room.ColumnInfo.Collate public abstract int collate() default androidx.room.ColumnInfo.UNSPECIFIED;
+ method public abstract String defaultValue() default androidx.room.ColumnInfo.VALUE_UNSPECIFIED;
+ method public abstract boolean index() default false;
+ method public abstract String name() default androidx.room.ColumnInfo.INHERIT_FIELD_NAME;
+ method @androidx.room.ColumnInfo.SQLiteTypeAffinity public abstract int typeAffinity() default androidx.room.ColumnInfo.UNDEFINED;
+ field public static final int BINARY = 2; // 0x2
+ field public static final int BLOB = 5; // 0x5
+ field public static final String INHERIT_FIELD_NAME = "[field-name]";
+ field public static final int INTEGER = 3; // 0x3
+ field @RequiresApi(21) public static final int LOCALIZED = 5; // 0x5
+ field public static final int NOCASE = 3; // 0x3
+ field public static final int REAL = 4; // 0x4
+ field public static final int RTRIM = 4; // 0x4
+ field public static final int TEXT = 2; // 0x2
+ field public static final int UNDEFINED = 1; // 0x1
+ field @RequiresApi(21) public static final int UNICODE = 6; // 0x6
+ field public static final int UNSPECIFIED = 1; // 0x1
+ field public static final String VALUE_UNSPECIFIED = "[value-unspecified]";
+ }
+
+ @IntDef({androidx.room.ColumnInfo.UNSPECIFIED, androidx.room.ColumnInfo.BINARY, androidx.room.ColumnInfo.NOCASE, androidx.room.ColumnInfo.RTRIM, androidx.room.ColumnInfo.LOCALIZED, androidx.room.ColumnInfo.UNICODE}) @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) public static @interface ColumnInfo.Collate {
+ }
+
+ @IntDef({androidx.room.ColumnInfo.UNDEFINED, androidx.room.ColumnInfo.TEXT, androidx.room.ColumnInfo.INTEGER, androidx.room.ColumnInfo.REAL, androidx.room.ColumnInfo.BLOB}) @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) public static @interface ColumnInfo.SQLiteTypeAffinity {
+ }
+
+ @java.lang.annotation.Target(java.lang.annotation.ElementType.TYPE) @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) public @interface Dao {
+ }
+
+ @java.lang.annotation.Target(java.lang.annotation.ElementType.TYPE) @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) public @interface Database {
+ method public abstract Class<?>[] entities();
+ method public abstract boolean exportSchema() default true;
+ method public abstract int version();
+ method public abstract Class<?>[] views() default {};
+ }
+
+ @java.lang.annotation.Target(java.lang.annotation.ElementType.TYPE) @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) public @interface DatabaseView {
+ method public abstract String value() default "";
+ method public abstract String viewName() default "";
+ }
+
+ @java.lang.annotation.Target(java.lang.annotation.ElementType.METHOD) @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) public @interface Delete {
+ method public abstract Class<?> entity() default java.lang.Object.class;
+ }
+
+ @java.lang.annotation.Target({java.lang.annotation.ElementType.FIELD, java.lang.annotation.ElementType.METHOD}) @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) public @interface Embedded {
+ method public abstract String prefix() default "";
+ }
+
+ @java.lang.annotation.Target(java.lang.annotation.ElementType.TYPE) @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) public @interface Entity {
+ method public abstract androidx.room.ForeignKey[] foreignKeys() default {};
+ method public abstract String[] ignoredColumns() default {};
+ method public abstract androidx.room.Index[] indices() default {};
+ method public abstract boolean inheritSuperIndices() default false;
+ method public abstract String[] primaryKeys() default {};
+ method public abstract String tableName() default "";
+ }
+
+ @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) public @interface ForeignKey {
+ method public abstract String[] childColumns();
+ method public abstract boolean deferred() default false;
+ method public abstract Class<?> entity();
+ method @androidx.room.ForeignKey.Action public abstract int onDelete() default androidx.room.ForeignKey.NO_ACTION;
+ method @androidx.room.ForeignKey.Action public abstract int onUpdate() default androidx.room.ForeignKey.NO_ACTION;
+ method public abstract String[] parentColumns();
+ field public static final int CASCADE = 5; // 0x5
+ field public static final int NO_ACTION = 1; // 0x1
+ field public static final int RESTRICT = 2; // 0x2
+ field public static final int SET_DEFAULT = 4; // 0x4
+ field public static final int SET_NULL = 3; // 0x3
+ }
+
+ @IntDef({androidx.room.ForeignKey.NO_ACTION, androidx.room.ForeignKey.RESTRICT, androidx.room.ForeignKey.SET_NULL, androidx.room.ForeignKey.SET_DEFAULT, androidx.room.ForeignKey.CASCADE}) @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) public static @interface ForeignKey.Action {
+ }
+
+ @RequiresApi(16) @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) @java.lang.annotation.Target(java.lang.annotation.ElementType.TYPE) public @interface Fts3 {
+ method public abstract String tokenizer() default androidx.room.FtsOptions.TOKENIZER_SIMPLE;
+ method public abstract String[] tokenizerArgs() default {};
+ }
+
+ @RequiresApi(16) @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) @java.lang.annotation.Target(java.lang.annotation.ElementType.TYPE) public @interface Fts4 {
+ method public abstract Class<?> contentEntity() default java.lang.Object.class;
+ method public abstract String languageId() default "";
+ method public abstract androidx.room.FtsOptions.MatchInfo matchInfo() default androidx.room.FtsOptions.MatchInfo.FTS4;
+ method public abstract String[] notIndexed() default {};
+ method public abstract androidx.room.FtsOptions.Order order() default androidx.room.FtsOptions.Order.ASC;
+ method public abstract int[] prefix() default {};
+ method public abstract String tokenizer() default androidx.room.FtsOptions.TOKENIZER_SIMPLE;
+ method public abstract String[] tokenizerArgs() default {};
+ }
+
+ public class FtsOptions {
+ field public static final String TOKENIZER_ICU = "icu";
+ field public static final String TOKENIZER_PORTER = "porter";
+ field public static final String TOKENIZER_SIMPLE = "simple";
+ field @RequiresApi(21) public static final String TOKENIZER_UNICODE61 = "unicode61";
+ }
+
+ public enum FtsOptions.MatchInfo {
+ enum_constant public static final androidx.room.FtsOptions.MatchInfo FTS3;
+ enum_constant public static final androidx.room.FtsOptions.MatchInfo FTS4;
+ }
+
+ public enum FtsOptions.Order {
+ enum_constant public static final androidx.room.FtsOptions.Order ASC;
+ enum_constant public static final androidx.room.FtsOptions.Order DESC;
+ }
+
+ @java.lang.annotation.Target({java.lang.annotation.ElementType.METHOD, java.lang.annotation.ElementType.FIELD, java.lang.annotation.ElementType.CONSTRUCTOR}) @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) public @interface Ignore {
+ }
+
+ @java.lang.annotation.Target({}) @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) public @interface Index {
+ method public abstract String name() default "";
+ method public abstract boolean unique() default false;
+ method public abstract String[] value();
+ }
+
+ @java.lang.annotation.Target({java.lang.annotation.ElementType.METHOD}) @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) public @interface Insert {
+ method public abstract Class<?> entity() default java.lang.Object.class;
+ method @androidx.room.OnConflictStrategy public abstract int onConflict() default androidx.room.OnConflictStrategy.ABORT;
+ }
+
+ @java.lang.annotation.Target({}) @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) public @interface Junction {
+ method public abstract String entityColumn() default "";
+ method public abstract String parentColumn() default "";
+ method public abstract Class<?> value();
+ }
+
+ @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) @IntDef({androidx.room.OnConflictStrategy.REPLACE, androidx.room.OnConflictStrategy.ROLLBACK, androidx.room.OnConflictStrategy.ABORT, androidx.room.OnConflictStrategy.FAIL, androidx.room.OnConflictStrategy.IGNORE}) public @interface OnConflictStrategy {
+ field public static final int ABORT = 3; // 0x3
+ field @Deprecated public static final int FAIL = 4; // 0x4
+ field public static final int IGNORE = 5; // 0x5
+ field public static final int REPLACE = 1; // 0x1
+ field @Deprecated public static final int ROLLBACK = 2; // 0x2
+ }
+
+ @java.lang.annotation.Target({java.lang.annotation.ElementType.FIELD, java.lang.annotation.ElementType.METHOD}) @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) public @interface PrimaryKey {
+ method public abstract boolean autoGenerate() default false;
+ }
+
+ @java.lang.annotation.Target(java.lang.annotation.ElementType.METHOD) @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) public @interface Query {
+ method public abstract String value();
+ }
+
+ @java.lang.annotation.Target(java.lang.annotation.ElementType.METHOD) @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) public @interface RawQuery {
+ method public abstract Class<?>[] observedEntities() default {};
+ }
+
+ @java.lang.annotation.Target({java.lang.annotation.ElementType.FIELD, java.lang.annotation.ElementType.METHOD}) @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) public @interface Relation {
+ method public abstract androidx.room.Junction associateBy() default @androidx.room.Junction;
+ method public abstract Class<?> entity() default java.lang.Object.class;
+ method public abstract String entityColumn();
+ method public abstract String parentColumn();
+ method public abstract String[] projection() default {};
+ }
+
+ public class RoomWarnings {
+ ctor @Deprecated public RoomWarnings();
+ field public static final String CANNOT_CREATE_VERIFICATION_DATABASE = "ROOM_CANNOT_CREATE_VERIFICATION_DATABASE";
+ field public static final String CURSOR_MISMATCH = "ROOM_CURSOR_MISMATCH";
+ field public static final String DEFAULT_CONSTRUCTOR = "ROOM_DEFAULT_CONSTRUCTOR";
+ field public static final String INDEX_FROM_EMBEDDED_ENTITY_IS_DROPPED = "ROOM_EMBEDDED_ENTITY_INDEX_IS_DROPPED";
+ field public static final String INDEX_FROM_EMBEDDED_FIELD_IS_DROPPED = "ROOM_EMBEDDED_INDEX_IS_DROPPED";
+ field public static final String INDEX_FROM_PARENT_FIELD_IS_DROPPED = "ROOM_PARENT_FIELD_INDEX_IS_DROPPED";
+ field public static final String INDEX_FROM_PARENT_IS_DROPPED = "ROOM_PARENT_INDEX_IS_DROPPED";
+ field public static final String MISSING_INDEX_ON_FOREIGN_KEY_CHILD = "ROOM_MISSING_FOREIGN_KEY_CHILD_INDEX";
+ field public static final String MISSING_JAVA_TMP_DIR = "ROOM_MISSING_JAVA_TMP_DIR";
+ field public static final String MISSING_SCHEMA_LOCATION = "ROOM_MISSING_SCHEMA_LOCATION";
+ field public static final String PRIMARY_KEY_FROM_EMBEDDED_IS_DROPPED = "ROOM_EMBEDDED_PRIMARY_KEY_IS_DROPPED";
+ field public static final String RELATION_QUERY_WITHOUT_TRANSACTION = "ROOM_RELATION_QUERY_WITHOUT_TRANSACTION";
+ field public static final String RELATION_TYPE_MISMATCH = "ROOM_RELATION_TYPE_MISMATCH";
+ }
+
+ @java.lang.annotation.Target({java.lang.annotation.ElementType.METHOD, java.lang.annotation.ElementType.TYPE}) @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) public @interface SkipQueryVerification {
+ }
+
+ @java.lang.annotation.Target({java.lang.annotation.ElementType.METHOD}) @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) public @interface Transaction {
+ }
+
+ @java.lang.annotation.Target({java.lang.annotation.ElementType.METHOD}) @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) public @interface TypeConverter {
+ }
+
+ @java.lang.annotation.Target({java.lang.annotation.ElementType.METHOD, java.lang.annotation.ElementType.PARAMETER, java.lang.annotation.ElementType.TYPE, java.lang.annotation.ElementType.FIELD}) @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) public @interface TypeConverters {
+ method public abstract Class<?>[] value();
+ }
+
+ @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) public @interface Update {
+ method public abstract Class<?> entity() default java.lang.Object.class;
+ method @androidx.room.OnConflictStrategy public abstract int onConflict() default androidx.room.OnConflictStrategy.ABORT;
+ }
+
+}
+
diff --git a/room/common/api/restricted_2.2.0-beta01.txt b/room/common/api/restricted_2.2.0-beta01.txt
new file mode 100644
index 0000000..ed6c4ee
--- /dev/null
+++ b/room/common/api/restricted_2.2.0-beta01.txt
@@ -0,0 +1,206 @@
+// Signature format: 3.0
+package androidx.room {
+
+ @java.lang.annotation.Target({java.lang.annotation.ElementType.FIELD, java.lang.annotation.ElementType.METHOD}) @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) public @interface ColumnInfo {
+ method @androidx.room.ColumnInfo.Collate public abstract int collate() default androidx.room.ColumnInfo.UNSPECIFIED;
+ method public abstract String defaultValue() default androidx.room.ColumnInfo.VALUE_UNSPECIFIED;
+ method public abstract boolean index() default false;
+ method public abstract String name() default androidx.room.ColumnInfo.INHERIT_FIELD_NAME;
+ method @androidx.room.ColumnInfo.SQLiteTypeAffinity public abstract int typeAffinity() default androidx.room.ColumnInfo.UNDEFINED;
+ field public static final int BINARY = 2; // 0x2
+ field public static final int BLOB = 5; // 0x5
+ field public static final String INHERIT_FIELD_NAME = "[field-name]";
+ field public static final int INTEGER = 3; // 0x3
+ field @RequiresApi(21) public static final int LOCALIZED = 5; // 0x5
+ field public static final int NOCASE = 3; // 0x3
+ field public static final int REAL = 4; // 0x4
+ field public static final int RTRIM = 4; // 0x4
+ field public static final int TEXT = 2; // 0x2
+ field public static final int UNDEFINED = 1; // 0x1
+ field @RequiresApi(21) public static final int UNICODE = 6; // 0x6
+ field public static final int UNSPECIFIED = 1; // 0x1
+ field public static final String VALUE_UNSPECIFIED = "[value-unspecified]";
+ }
+
+ @IntDef({androidx.room.ColumnInfo.UNSPECIFIED, androidx.room.ColumnInfo.BINARY, androidx.room.ColumnInfo.NOCASE, androidx.room.ColumnInfo.RTRIM, androidx.room.ColumnInfo.LOCALIZED, androidx.room.ColumnInfo.UNICODE}) @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) public static @interface ColumnInfo.Collate {
+ }
+
+ @IntDef({androidx.room.ColumnInfo.UNDEFINED, androidx.room.ColumnInfo.TEXT, androidx.room.ColumnInfo.INTEGER, androidx.room.ColumnInfo.REAL, androidx.room.ColumnInfo.BLOB}) @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) public static @interface ColumnInfo.SQLiteTypeAffinity {
+ }
+
+ @java.lang.annotation.Target(java.lang.annotation.ElementType.TYPE) @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) public @interface Dao {
+ }
+
+ @java.lang.annotation.Target(java.lang.annotation.ElementType.TYPE) @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) public @interface Database {
+ method public abstract Class<?>[] entities();
+ method public abstract boolean exportSchema() default true;
+ method public abstract int version();
+ method public abstract Class<?>[] views() default {};
+ }
+
+ @java.lang.annotation.Target(java.lang.annotation.ElementType.TYPE) @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) public @interface DatabaseView {
+ method public abstract String value() default "";
+ method public abstract String viewName() default "";
+ }
+
+ @java.lang.annotation.Target(java.lang.annotation.ElementType.METHOD) @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) public @interface Delete {
+ method public abstract Class<?> entity() default java.lang.Object.class;
+ }
+
+ @java.lang.annotation.Target({java.lang.annotation.ElementType.FIELD, java.lang.annotation.ElementType.METHOD}) @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) public @interface Embedded {
+ method public abstract String prefix() default "";
+ }
+
+ @java.lang.annotation.Target(java.lang.annotation.ElementType.TYPE) @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) public @interface Entity {
+ method public abstract androidx.room.ForeignKey[] foreignKeys() default {};
+ method public abstract String[] ignoredColumns() default {};
+ method public abstract androidx.room.Index[] indices() default {};
+ method public abstract boolean inheritSuperIndices() default false;
+ method public abstract String[] primaryKeys() default {};
+ method public abstract String tableName() default "";
+ }
+
+ @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) public @interface ForeignKey {
+ method public abstract String[] childColumns();
+ method public abstract boolean deferred() default false;
+ method public abstract Class<?> entity();
+ method @androidx.room.ForeignKey.Action public abstract int onDelete() default androidx.room.ForeignKey.NO_ACTION;
+ method @androidx.room.ForeignKey.Action public abstract int onUpdate() default androidx.room.ForeignKey.NO_ACTION;
+ method public abstract String[] parentColumns();
+ field public static final int CASCADE = 5; // 0x5
+ field public static final int NO_ACTION = 1; // 0x1
+ field public static final int RESTRICT = 2; // 0x2
+ field public static final int SET_DEFAULT = 4; // 0x4
+ field public static final int SET_NULL = 3; // 0x3
+ }
+
+ @IntDef({androidx.room.ForeignKey.NO_ACTION, androidx.room.ForeignKey.RESTRICT, androidx.room.ForeignKey.SET_NULL, androidx.room.ForeignKey.SET_DEFAULT, androidx.room.ForeignKey.CASCADE}) @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) public static @interface ForeignKey.Action {
+ }
+
+ @RequiresApi(16) @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) @java.lang.annotation.Target(java.lang.annotation.ElementType.TYPE) public @interface Fts3 {
+ method public abstract String tokenizer() default androidx.room.FtsOptions.TOKENIZER_SIMPLE;
+ method public abstract String[] tokenizerArgs() default {};
+ }
+
+ @RequiresApi(16) @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) @java.lang.annotation.Target(java.lang.annotation.ElementType.TYPE) public @interface Fts4 {
+ method public abstract Class<?> contentEntity() default java.lang.Object.class;
+ method public abstract String languageId() default "";
+ method public abstract androidx.room.FtsOptions.MatchInfo matchInfo() default androidx.room.FtsOptions.MatchInfo.FTS4;
+ method public abstract String[] notIndexed() default {};
+ method public abstract androidx.room.FtsOptions.Order order() default androidx.room.FtsOptions.Order.ASC;
+ method public abstract int[] prefix() default {};
+ method public abstract String tokenizer() default androidx.room.FtsOptions.TOKENIZER_SIMPLE;
+ method public abstract String[] tokenizerArgs() default {};
+ }
+
+ public class FtsOptions {
+ field public static final String TOKENIZER_ICU = "icu";
+ field public static final String TOKENIZER_PORTER = "porter";
+ field public static final String TOKENIZER_SIMPLE = "simple";
+ field @RequiresApi(21) public static final String TOKENIZER_UNICODE61 = "unicode61";
+ }
+
+ public enum FtsOptions.MatchInfo {
+ enum_constant public static final androidx.room.FtsOptions.MatchInfo FTS3;
+ enum_constant public static final androidx.room.FtsOptions.MatchInfo FTS4;
+ }
+
+ public enum FtsOptions.Order {
+ enum_constant public static final androidx.room.FtsOptions.Order ASC;
+ enum_constant public static final androidx.room.FtsOptions.Order DESC;
+ }
+
+ @java.lang.annotation.Target({java.lang.annotation.ElementType.METHOD, java.lang.annotation.ElementType.FIELD, java.lang.annotation.ElementType.CONSTRUCTOR}) @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) public @interface Ignore {
+ }
+
+ @java.lang.annotation.Target({}) @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) public @interface Index {
+ method public abstract String name() default "";
+ method public abstract boolean unique() default false;
+ method public abstract String[] value();
+ }
+
+ @java.lang.annotation.Target({java.lang.annotation.ElementType.METHOD}) @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) public @interface Insert {
+ method public abstract Class<?> entity() default java.lang.Object.class;
+ method @androidx.room.OnConflictStrategy public abstract int onConflict() default androidx.room.OnConflictStrategy.ABORT;
+ }
+
+ @java.lang.annotation.Target({}) @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) public @interface Junction {
+ method public abstract String entityColumn() default "";
+ method public abstract String parentColumn() default "";
+ method public abstract Class<?> value();
+ }
+
+ @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) @IntDef({androidx.room.OnConflictStrategy.REPLACE, androidx.room.OnConflictStrategy.ROLLBACK, androidx.room.OnConflictStrategy.ABORT, androidx.room.OnConflictStrategy.FAIL, androidx.room.OnConflictStrategy.IGNORE}) public @interface OnConflictStrategy {
+ field public static final int ABORT = 3; // 0x3
+ field @Deprecated public static final int FAIL = 4; // 0x4
+ field public static final int IGNORE = 5; // 0x5
+ field public static final int REPLACE = 1; // 0x1
+ field @Deprecated public static final int ROLLBACK = 2; // 0x2
+ }
+
+ @java.lang.annotation.Target({java.lang.annotation.ElementType.FIELD, java.lang.annotation.ElementType.METHOD}) @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) public @interface PrimaryKey {
+ method public abstract boolean autoGenerate() default false;
+ }
+
+ @java.lang.annotation.Target(java.lang.annotation.ElementType.METHOD) @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) public @interface Query {
+ method public abstract String value();
+ }
+
+ @java.lang.annotation.Target(java.lang.annotation.ElementType.METHOD) @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) public @interface RawQuery {
+ method public abstract Class<?>[] observedEntities() default {};
+ }
+
+ @java.lang.annotation.Target({java.lang.annotation.ElementType.FIELD, java.lang.annotation.ElementType.METHOD}) @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) public @interface Relation {
+ method public abstract androidx.room.Junction associateBy() default @androidx.room.Junction;
+ method public abstract Class<?> entity() default java.lang.Object.class;
+ method public abstract String entityColumn();
+ method public abstract String parentColumn();
+ method public abstract String[] projection() default {};
+ }
+
+ @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public class RoomMasterTable {
+ method public static String! createInsertQuery(String!);
+ field public static final String CREATE_QUERY = "CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)";
+ field public static final String DEFAULT_ID = "42";
+ field public static final String NAME = "room_master_table";
+ field public static final String READ_QUERY = "SELECT identity_hash FROM room_master_table WHERE id = 42 LIMIT 1";
+ field public static final String TABLE_NAME = "room_master_table";
+ }
+
+ public class RoomWarnings {
+ ctor @Deprecated public RoomWarnings();
+ field public static final String CANNOT_CREATE_VERIFICATION_DATABASE = "ROOM_CANNOT_CREATE_VERIFICATION_DATABASE";
+ field public static final String CURSOR_MISMATCH = "ROOM_CURSOR_MISMATCH";
+ field public static final String DEFAULT_CONSTRUCTOR = "ROOM_DEFAULT_CONSTRUCTOR";
+ field public static final String INDEX_FROM_EMBEDDED_ENTITY_IS_DROPPED = "ROOM_EMBEDDED_ENTITY_INDEX_IS_DROPPED";
+ field public static final String INDEX_FROM_EMBEDDED_FIELD_IS_DROPPED = "ROOM_EMBEDDED_INDEX_IS_DROPPED";
+ field public static final String INDEX_FROM_PARENT_FIELD_IS_DROPPED = "ROOM_PARENT_FIELD_INDEX_IS_DROPPED";
+ field public static final String INDEX_FROM_PARENT_IS_DROPPED = "ROOM_PARENT_INDEX_IS_DROPPED";
+ field public static final String MISSING_INDEX_ON_FOREIGN_KEY_CHILD = "ROOM_MISSING_FOREIGN_KEY_CHILD_INDEX";
+ field public static final String MISSING_JAVA_TMP_DIR = "ROOM_MISSING_JAVA_TMP_DIR";
+ field public static final String MISSING_SCHEMA_LOCATION = "ROOM_MISSING_SCHEMA_LOCATION";
+ field public static final String PRIMARY_KEY_FROM_EMBEDDED_IS_DROPPED = "ROOM_EMBEDDED_PRIMARY_KEY_IS_DROPPED";
+ field public static final String RELATION_QUERY_WITHOUT_TRANSACTION = "ROOM_RELATION_QUERY_WITHOUT_TRANSACTION";
+ field public static final String RELATION_TYPE_MISMATCH = "ROOM_RELATION_TYPE_MISMATCH";
+ }
+
+ @java.lang.annotation.Target({java.lang.annotation.ElementType.METHOD, java.lang.annotation.ElementType.TYPE}) @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) public @interface SkipQueryVerification {
+ }
+
+ @java.lang.annotation.Target({java.lang.annotation.ElementType.METHOD}) @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) public @interface Transaction {
+ }
+
+ @java.lang.annotation.Target({java.lang.annotation.ElementType.METHOD}) @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) public @interface TypeConverter {
+ }
+
+ @java.lang.annotation.Target({java.lang.annotation.ElementType.METHOD, java.lang.annotation.ElementType.PARAMETER, java.lang.annotation.ElementType.TYPE, java.lang.annotation.ElementType.FIELD}) @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) public @interface TypeConverters {
+ method public abstract Class<?>[] value();
+ }
+
+ @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) public @interface Update {
+ method public abstract Class<?> entity() default java.lang.Object.class;
+ method @androidx.room.OnConflictStrategy public abstract int onConflict() default androidx.room.OnConflictStrategy.ABORT;
+ }
+
+}
+
diff --git a/preference/ktx/api/restricted_1.1.0-alpha06.txt b/room/guava/api/2.2.0-beta01.txt
similarity index 100%
copy from preference/ktx/api/restricted_1.1.0-alpha06.txt
copy to room/guava/api/2.2.0-beta01.txt
diff --git a/biometric/api/res-1.0.0-beta01.txt b/room/guava/api/res-2.2.0-beta01.txt
similarity index 100%
copy from biometric/api/res-1.0.0-beta01.txt
copy to room/guava/api/res-2.2.0-beta01.txt
diff --git a/room/guava/api/restricted_2.2.0-beta01.txt b/room/guava/api/restricted_2.2.0-beta01.txt
new file mode 100644
index 0000000..b39d410
--- /dev/null
+++ b/room/guava/api/restricted_2.2.0-beta01.txt
@@ -0,0 +1,14 @@
+// Signature format: 3.0
+package androidx.room.guava {
+
+ @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public class GuavaRoom {
+ method @Deprecated public static <T> com.google.common.util.concurrent.ListenableFuture<T!>! createListenableFuture(java.util.concurrent.Callable<T!>!, androidx.room.RoomSQLiteQuery!, boolean);
+ method @Deprecated public static <T> com.google.common.util.concurrent.ListenableFuture<T!>! createListenableFuture(androidx.room.RoomDatabase!, java.util.concurrent.Callable<T!>!, androidx.room.RoomSQLiteQuery!, boolean);
+ method public static <T> com.google.common.util.concurrent.ListenableFuture<T!>! createListenableFuture(androidx.room.RoomDatabase!, boolean, java.util.concurrent.Callable<T!>!, androidx.room.RoomSQLiteQuery!, boolean);
+ method public static <T> com.google.common.util.concurrent.ListenableFuture<T!> createListenableFuture(androidx.room.RoomDatabase, boolean, java.util.concurrent.Callable<T!>, androidx.room.RoomSQLiteQuery, boolean, android.os.CancellationSignal?);
+ method @Deprecated public static <T> com.google.common.util.concurrent.ListenableFuture<T!> createListenableFuture(androidx.room.RoomDatabase, java.util.concurrent.Callable<T!>);
+ method public static <T> com.google.common.util.concurrent.ListenableFuture<T!> createListenableFuture(androidx.room.RoomDatabase, boolean, java.util.concurrent.Callable<T!>);
+ }
+
+}
+
diff --git a/room/guava/build.gradle b/room/guava/build.gradle
index 4de80a2..a167763 100644
--- a/room/guava/build.gradle
+++ b/room/guava/build.gradle
@@ -33,7 +33,7 @@
implementation(ARCH_CORE_RUNTIME)
api(SUPPORT_ANNOTATIONS)
- api(GUAVA_ANDROID)
+ implementation(GUAVA_ANDROID)
androidTestImplementation(ANDROIDX_TEST_RUNNER)
androidTestImplementation(TRUTH)
}
diff --git a/room/guava/src/androidTest/java/androidx/room/guava/GuavaRoomTest.java b/room/guava/src/androidTest/java/androidx/room/guava/GuavaRoomTest.java
index 8ae5983..653a6b9 100644
--- a/room/guava/src/androidTest/java/androidx/room/guava/GuavaRoomTest.java
+++ b/room/guava/src/androidTest/java/androidx/room/guava/GuavaRoomTest.java
@@ -25,6 +25,7 @@
import androidx.room.InvalidationTracker;
import androidx.room.RoomDatabase;
import androidx.sqlite.db.SupportSQLiteOpenHelper;
+import androidx.test.filters.SdkSuppress;
import androidx.test.filters.SmallTest;
import com.google.common.util.concurrent.ListenableFuture;
@@ -37,6 +38,7 @@
public class GuavaRoomTest {
@Test
+ @SdkSuppress(minSdkVersion = 16)
public void queryIsCancelled() {
Executor executor = runnable -> { /* nothing to do */ };
diff --git a/room/ktx/api/2.2.0-beta01.txt b/room/ktx/api/2.2.0-beta01.txt
new file mode 100644
index 0000000..cee69e3
--- /dev/null
+++ b/room/ktx/api/2.2.0-beta01.txt
@@ -0,0 +1,14 @@
+// Signature format: 3.0
+package androidx.room {
+
+ public final class CoroutinesRoomKt {
+ ctor public CoroutinesRoomKt();
+ }
+
+ public final class RoomDatabaseKt {
+ ctor public RoomDatabaseKt();
+ method public static suspend <R> Object! withTransaction(androidx.room.RoomDatabase, kotlin.jvm.functions.Function1<? super kotlin.coroutines.Continuation<? super R>,?> block, kotlin.coroutines.Continuation<? super R> p);
+ }
+
+}
+
diff --git a/preference/ktx/api/res-1.1.0-beta01.txt b/room/ktx/api/res-2.2.0-beta01.txt
similarity index 100%
copy from preference/ktx/api/res-1.1.0-beta01.txt
copy to room/ktx/api/res-2.2.0-beta01.txt
diff --git a/room/ktx/api/restricted_2.2.0-beta01.txt b/room/ktx/api/restricted_2.2.0-beta01.txt
new file mode 100644
index 0000000..20837edc
--- /dev/null
+++ b/room/ktx/api/restricted_2.2.0-beta01.txt
@@ -0,0 +1,25 @@
+// Signature format: 3.0
+package androidx.room {
+
+ @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public final class CoroutinesRoom {
+ method public static <R> kotlinx.coroutines.flow.Flow<R> createFlow(androidx.room.RoomDatabase db, boolean inTransaction, String![] tableNames, java.util.concurrent.Callable<R> callable);
+ method public static suspend <R> Object! execute(androidx.room.RoomDatabase p, boolean db, java.util.concurrent.Callable<R> inTransaction, kotlin.coroutines.Continuation<? super R> callable);
+ field public static final androidx.room.CoroutinesRoom.Companion! Companion;
+ }
+
+ public static final class CoroutinesRoom.Companion {
+ method public <R> kotlinx.coroutines.flow.Flow<R> createFlow(androidx.room.RoomDatabase db, boolean inTransaction, String![] tableNames, java.util.concurrent.Callable<R> callable);
+ method public suspend <R> Object! execute(androidx.room.RoomDatabase db, boolean inTransaction, java.util.concurrent.Callable<R> callable, kotlin.coroutines.Continuation<? super R> p);
+ }
+
+ public final class CoroutinesRoomKt {
+ ctor public CoroutinesRoomKt();
+ }
+
+ public final class RoomDatabaseKt {
+ ctor public RoomDatabaseKt();
+ method public static suspend <R> Object! withTransaction(androidx.room.RoomDatabase, kotlin.jvm.functions.Function1<? super kotlin.coroutines.Continuation<? super R>,?> block, kotlin.coroutines.Continuation<? super R> p);
+ }
+
+}
+
diff --git a/preference/ktx/api/restricted_1.1.0-alpha06.txt b/room/migration/api/2.2.0-beta01.txt
similarity index 100%
copy from preference/ktx/api/restricted_1.1.0-alpha06.txt
copy to room/migration/api/2.2.0-beta01.txt
diff --git a/room/migration/api/restricted_2.2.0-beta01.txt b/room/migration/api/restricted_2.2.0-beta01.txt
new file mode 100644
index 0000000..ae8f58d
--- /dev/null
+++ b/room/migration/api/restricted_2.2.0-beta01.txt
@@ -0,0 +1,107 @@
+// Signature format: 3.0
+package androidx.room.migration.bundle {
+
+ @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public class BundleUtil {
+ field public static final String TABLE_NAME_PLACEHOLDER = "${TABLE_NAME}";
+ field public static final String VIEW_NAME_PLACEHOLDER = "${VIEW_NAME}";
+ }
+
+ @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public class DatabaseBundle {
+ ctor public DatabaseBundle(int, String!, java.util.List<androidx.room.migration.bundle.EntityBundle!>!, java.util.List<androidx.room.migration.bundle.DatabaseViewBundle!>!, java.util.List<java.lang.String!>!);
+ ctor public DatabaseBundle();
+ method public java.util.List<java.lang.String!>! buildCreateQueries();
+ method public java.util.List<androidx.room.migration.bundle.EntityBundle!>! getEntities();
+ method public java.util.Map<java.lang.String!,androidx.room.migration.bundle.EntityBundle!>! getEntitiesByTableName();
+ method public String! getIdentityHash();
+ method public int getVersion();
+ method public java.util.List<androidx.room.migration.bundle.DatabaseViewBundle!>! getViews();
+ method public boolean isSchemaEqual(androidx.room.migration.bundle.DatabaseBundle!);
+ }
+
+ @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public class DatabaseViewBundle {
+ ctor public DatabaseViewBundle(String!, String!);
+ method public String! createView();
+ method public String! getCreateSql();
+ method public String! getViewName();
+ method public boolean isSchemaEqual(androidx.room.migration.bundle.DatabaseViewBundle!);
+ }
+
+ @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public class EntityBundle {
+ ctor public EntityBundle(String!, String!, java.util.List<androidx.room.migration.bundle.FieldBundle!>!, androidx.room.migration.bundle.PrimaryKeyBundle!, java.util.List<androidx.room.migration.bundle.IndexBundle!>!, java.util.List<androidx.room.migration.bundle.ForeignKeyBundle!>!);
+ method public java.util.Collection<java.lang.String!>! buildCreateQueries();
+ method public String! createNewTable();
+ method public String! createTable();
+ method public String! getCreateSql();
+ method public java.util.List<androidx.room.migration.bundle.FieldBundle!>! getFields();
+ method public java.util.Map<java.lang.String!,androidx.room.migration.bundle.FieldBundle!>! getFieldsByColumnName();
+ method public java.util.List<androidx.room.migration.bundle.ForeignKeyBundle!>! getForeignKeys();
+ method public java.util.List<androidx.room.migration.bundle.IndexBundle!>! getIndices();
+ method public String! getNewTableName();
+ method public androidx.room.migration.bundle.PrimaryKeyBundle! getPrimaryKey();
+ method public String! getTableName();
+ method public boolean isSchemaEqual(androidx.room.migration.bundle.EntityBundle!);
+ method public String renameToOriginal();
+ }
+
+ @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public class FieldBundle {
+ ctor @Deprecated public FieldBundle(String!, String!, String!, boolean);
+ ctor public FieldBundle(String!, String!, String!, boolean, String!);
+ method public String! getAffinity();
+ method public String! getColumnName();
+ method public String! getDefaultValue();
+ method public String! getFieldPath();
+ method public boolean isNonNull();
+ method public boolean isSchemaEqual(androidx.room.migration.bundle.FieldBundle!);
+ }
+
+ @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public class ForeignKeyBundle {
+ ctor public ForeignKeyBundle(String!, String!, String!, java.util.List<java.lang.String!>!, java.util.List<java.lang.String!>!);
+ method public java.util.List<java.lang.String!>! getColumns();
+ method public String! getOnDelete();
+ method public String! getOnUpdate();
+ method public java.util.List<java.lang.String!>! getReferencedColumns();
+ method public String! getTable();
+ method public boolean isSchemaEqual(androidx.room.migration.bundle.ForeignKeyBundle!);
+ }
+
+ @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public class FtsEntityBundle extends androidx.room.migration.bundle.EntityBundle {
+ ctor public FtsEntityBundle(String!, String!, java.util.List<androidx.room.migration.bundle.FieldBundle!>!, androidx.room.migration.bundle.PrimaryKeyBundle!, String!, androidx.room.migration.bundle.FtsOptionsBundle!, java.util.List<java.lang.String!>!);
+ method public androidx.room.migration.bundle.FtsOptionsBundle! getFtsOptions();
+ method public java.util.List<java.lang.String!>! getShadowTableNames();
+ }
+
+ @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public class FtsOptionsBundle {
+ ctor public FtsOptionsBundle(String!, java.util.List<java.lang.String!>!, String!, String!, String!, java.util.List<java.lang.String!>!, java.util.List<java.lang.Integer!>!, String!);
+ method public String! getContentTable();
+ method public boolean isSchemaEqual(androidx.room.migration.bundle.FtsOptionsBundle!);
+ }
+
+ @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public class IndexBundle {
+ ctor public IndexBundle(String!, boolean, java.util.List<java.lang.String!>!, String!);
+ method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public String! create(String!);
+ method public java.util.List<java.lang.String!>! getColumnNames();
+ method public String! getName();
+ method public boolean isSchemaEqual(androidx.room.migration.bundle.IndexBundle!);
+ method public boolean isUnique();
+ field public static final String DEFAULT_PREFIX = "index_";
+ }
+
+ @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public class PrimaryKeyBundle {
+ ctor public PrimaryKeyBundle(boolean, java.util.List<java.lang.String!>!);
+ method public java.util.List<java.lang.String!>! getColumnNames();
+ method public boolean isAutoGenerate();
+ method public boolean isSchemaEqual(androidx.room.migration.bundle.PrimaryKeyBundle!);
+ }
+
+ @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public class SchemaBundle {
+ ctor public SchemaBundle(int, androidx.room.migration.bundle.DatabaseBundle!);
+ method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public static androidx.room.migration.bundle.SchemaBundle! deserialize(java.io.InputStream!) throws java.io.UnsupportedEncodingException;
+ method public androidx.room.migration.bundle.DatabaseBundle! getDatabase();
+ method public int getFormatVersion();
+ method public boolean isSchemaEqual(androidx.room.migration.bundle.SchemaBundle!);
+ method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public static void serialize(androidx.room.migration.bundle.SchemaBundle!, java.io.File!) throws java.io.IOException;
+ field public static final int LATEST_FORMAT = 1; // 0x1
+ }
+
+}
+
diff --git a/room/runtime/api/2.2.0-beta01.txt b/room/runtime/api/2.2.0-beta01.txt
new file mode 100644
index 0000000..1d210e5
--- /dev/null
+++ b/room/runtime/api/2.2.0-beta01.txt
@@ -0,0 +1,117 @@
+// Signature format: 3.0
+package androidx.room {
+
+ public class DatabaseConfiguration {
+ method public boolean isMigrationRequired(int, int);
+ method @Deprecated public boolean isMigrationRequiredFrom(int);
+ field public final boolean allowDestructiveMigrationOnDowngrade;
+ field public final boolean allowMainThreadQueries;
+ field public final java.util.List<androidx.room.RoomDatabase.Callback!>? callbacks;
+ field public final android.content.Context context;
+ field public final String? copyFromAssetPath;
+ field public final java.io.File? copyFromFile;
+ field public final androidx.room.RoomDatabase.JournalMode! journalMode;
+ field public final androidx.room.RoomDatabase.MigrationContainer migrationContainer;
+ field public final boolean multiInstanceInvalidation;
+ field public final String? name;
+ field public final java.util.concurrent.Executor queryExecutor;
+ field public final boolean requireMigration;
+ field public final androidx.sqlite.db.SupportSQLiteOpenHelper.Factory sqliteOpenHelperFactory;
+ field public final java.util.concurrent.Executor transactionExecutor;
+ }
+
+ public class InvalidationTracker {
+ method @WorkerThread public void addObserver(androidx.room.InvalidationTracker.Observer);
+ method public void refreshVersionsAsync();
+ method @WorkerThread public void removeObserver(androidx.room.InvalidationTracker.Observer);
+ }
+
+ public abstract static class InvalidationTracker.Observer {
+ ctor protected InvalidationTracker.Observer(String, java.lang.String!...);
+ ctor public InvalidationTracker.Observer(String![]);
+ method public abstract void onInvalidated(java.util.Set<java.lang.String!>);
+ }
+
+ public class Room {
+ ctor @Deprecated public Room();
+ method public static <T extends androidx.room.RoomDatabase> androidx.room.RoomDatabase.Builder<T!> databaseBuilder(android.content.Context, Class<T!>, String);
+ method public static <T extends androidx.room.RoomDatabase> androidx.room.RoomDatabase.Builder<T!> inMemoryDatabaseBuilder(android.content.Context, Class<T!>);
+ field public static final String MASTER_TABLE_NAME = "room_master_table";
+ }
+
+ public abstract class RoomDatabase {
+ ctor public RoomDatabase();
+ method @Deprecated public void beginTransaction();
+ method @WorkerThread public abstract void clearAllTables();
+ method public void close();
+ method public androidx.sqlite.db.SupportSQLiteStatement! compileStatement(String);
+ method protected abstract androidx.room.InvalidationTracker createInvalidationTracker();
+ method protected abstract androidx.sqlite.db.SupportSQLiteOpenHelper createOpenHelper(androidx.room.DatabaseConfiguration!);
+ method @Deprecated public void endTransaction();
+ method public androidx.room.InvalidationTracker getInvalidationTracker();
+ method public androidx.sqlite.db.SupportSQLiteOpenHelper getOpenHelper();
+ method public java.util.concurrent.Executor getQueryExecutor();
+ method public java.util.concurrent.Executor getTransactionExecutor();
+ method public boolean inTransaction();
+ method @CallSuper public void init(androidx.room.DatabaseConfiguration);
+ method protected void internalInitInvalidationTracker(androidx.sqlite.db.SupportSQLiteDatabase);
+ method public boolean isOpen();
+ method public android.database.Cursor query(String, Object![]?);
+ method public android.database.Cursor query(androidx.sqlite.db.SupportSQLiteQuery);
+ method public android.database.Cursor query(androidx.sqlite.db.SupportSQLiteQuery, android.os.CancellationSignal?);
+ method public void runInTransaction(Runnable);
+ method public <V> V! runInTransaction(java.util.concurrent.Callable<V!>);
+ method @Deprecated public void setTransactionSuccessful();
+ field @Deprecated protected java.util.List<androidx.room.RoomDatabase.Callback!>? mCallbacks;
+ field @Deprecated protected volatile androidx.sqlite.db.SupportSQLiteDatabase! mDatabase;
+ }
+
+ public static class RoomDatabase.Builder<T extends androidx.room.RoomDatabase> {
+ method public androidx.room.RoomDatabase.Builder<T!> addCallback(androidx.room.RoomDatabase.Callback);
+ method public androidx.room.RoomDatabase.Builder<T!> addMigrations(androidx.room.migration.Migration!...);
+ method public androidx.room.RoomDatabase.Builder<T!> allowMainThreadQueries();
+ method public T build();
+ method public androidx.room.RoomDatabase.Builder<T!> createFromAsset(String);
+ method public androidx.room.RoomDatabase.Builder<T!> createFromFile(java.io.File);
+ method public androidx.room.RoomDatabase.Builder<T!> enableMultiInstanceInvalidation();
+ method public androidx.room.RoomDatabase.Builder<T!> fallbackToDestructiveMigration();
+ method public androidx.room.RoomDatabase.Builder<T!> fallbackToDestructiveMigrationFrom(int...);
+ method public androidx.room.RoomDatabase.Builder<T!> fallbackToDestructiveMigrationOnDowngrade();
+ method public androidx.room.RoomDatabase.Builder<T!> openHelperFactory(androidx.sqlite.db.SupportSQLiteOpenHelper.Factory?);
+ method public androidx.room.RoomDatabase.Builder<T!> setJournalMode(androidx.room.RoomDatabase.JournalMode);
+ method public androidx.room.RoomDatabase.Builder<T!> setQueryExecutor(java.util.concurrent.Executor);
+ method public androidx.room.RoomDatabase.Builder<T!> setTransactionExecutor(java.util.concurrent.Executor);
+ }
+
+ public abstract static class RoomDatabase.Callback {
+ ctor public RoomDatabase.Callback();
+ method public void onCreate(androidx.sqlite.db.SupportSQLiteDatabase);
+ method public void onDestructiveMigration(androidx.sqlite.db.SupportSQLiteDatabase);
+ method public void onOpen(androidx.sqlite.db.SupportSQLiteDatabase);
+ }
+
+ public enum RoomDatabase.JournalMode {
+ enum_constant public static final androidx.room.RoomDatabase.JournalMode AUTOMATIC;
+ enum_constant public static final androidx.room.RoomDatabase.JournalMode TRUNCATE;
+ enum_constant @RequiresApi(android.os.Build.VERSION_CODES.JELLY_BEAN) public static final androidx.room.RoomDatabase.JournalMode WRITE_AHEAD_LOGGING;
+ }
+
+ public static class RoomDatabase.MigrationContainer {
+ ctor public RoomDatabase.MigrationContainer();
+ method public void addMigrations(androidx.room.migration.Migration!...);
+ method public java.util.List<androidx.room.migration.Migration!>? findMigrationPath(int, int);
+ }
+
+}
+
+package androidx.room.migration {
+
+ public abstract class Migration {
+ ctor public Migration(int, int);
+ method public abstract void migrate(androidx.sqlite.db.SupportSQLiteDatabase);
+ field public final int endVersion;
+ field public final int startVersion;
+ }
+
+}
+
diff --git a/biometric/api/res-1.0.0-beta01.txt b/room/runtime/api/res-2.2.0-beta01.txt
similarity index 100%
copy from biometric/api/res-1.0.0-beta01.txt
copy to room/runtime/api/res-2.2.0-beta01.txt
diff --git a/room/runtime/api/restricted_2.2.0-beta01.ignore b/room/runtime/api/restricted_2.2.0-beta01.ignore
new file mode 100644
index 0000000..6984c60
--- /dev/null
+++ b/room/runtime/api/restricted_2.2.0-beta01.ignore
@@ -0,0 +1,19 @@
+// Baseline format: 1.0
+ChangedType: androidx.room.InvalidationTracker#createLiveData(String[], boolean, java.util.concurrent.Callable<T>):
+ Method androidx.room.InvalidationTracker.createLiveData has changed return type from LiveData<T> to androidx.lifecycle.LiveData<T>
+ChangedType: androidx.room.InvalidationTracker#createLiveData(String[], java.util.concurrent.Callable<T>):
+ Method androidx.room.InvalidationTracker.createLiveData has changed return type from LiveData<T> to androidx.lifecycle.LiveData<T>
+
+
+InvalidNullConversion: androidx.room.InvalidationTracker#createLiveData(String[], boolean, java.util.concurrent.Callable<T>):
+ Attempted to remove @NonNull annotation from method androidx.room.InvalidationTracker.createLiveData(String[],boolean,java.util.concurrent.Callable<T>)
+InvalidNullConversion: androidx.room.InvalidationTracker#createLiveData(String[], java.util.concurrent.Callable<T>):
+ Attempted to remove @NonNull annotation from method androidx.room.InvalidationTracker.createLiveData(String[],java.util.concurrent.Callable<T>)
+
+
+RemovedMethod: androidx.room.paging.LimitOffsetDataSource#isInvalid():
+ Removed method androidx.room.paging.LimitOffsetDataSource.isInvalid()
+RemovedMethod: androidx.room.paging.LimitOffsetDataSource#loadInitial(LoadInitialParams, LoadInitialCallback<T>):
+ Removed method androidx.room.paging.LimitOffsetDataSource.loadInitial(LoadInitialParams,LoadInitialCallback<T>)
+RemovedMethod: androidx.room.paging.LimitOffsetDataSource#loadRange(LoadRangeParams, LoadRangeCallback<T>):
+ Removed method androidx.room.paging.LimitOffsetDataSource.loadRange(LoadRangeParams,LoadRangeCallback<T>)
diff --git a/room/runtime/api/restricted_2.2.0-beta01.txt b/room/runtime/api/restricted_2.2.0-beta01.txt
new file mode 100644
index 0000000..6bc1371
--- /dev/null
+++ b/room/runtime/api/restricted_2.2.0-beta01.txt
@@ -0,0 +1,318 @@
+// Signature format: 3.0
+package androidx.room {
+
+ public class DatabaseConfiguration {
+ ctor @Deprecated @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public DatabaseConfiguration(android.content.Context, String?, androidx.sqlite.db.SupportSQLiteOpenHelper.Factory, androidx.room.RoomDatabase.MigrationContainer, java.util.List<androidx.room.RoomDatabase.Callback!>?, boolean, androidx.room.RoomDatabase.JournalMode!, java.util.concurrent.Executor, boolean, java.util.Set<java.lang.Integer!>?);
+ ctor @Deprecated @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public DatabaseConfiguration(android.content.Context, String?, androidx.sqlite.db.SupportSQLiteOpenHelper.Factory, androidx.room.RoomDatabase.MigrationContainer, java.util.List<androidx.room.RoomDatabase.Callback!>?, boolean, androidx.room.RoomDatabase.JournalMode!, java.util.concurrent.Executor, java.util.concurrent.Executor, boolean, boolean, boolean, java.util.Set<java.lang.Integer!>?);
+ ctor @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public DatabaseConfiguration(android.content.Context, String?, androidx.sqlite.db.SupportSQLiteOpenHelper.Factory, androidx.room.RoomDatabase.MigrationContainer, java.util.List<androidx.room.RoomDatabase.Callback!>?, boolean, androidx.room.RoomDatabase.JournalMode!, java.util.concurrent.Executor, java.util.concurrent.Executor, boolean, boolean, boolean, java.util.Set<java.lang.Integer!>?, String?, java.io.File?);
+ method public boolean isMigrationRequired(int, int);
+ method @Deprecated public boolean isMigrationRequiredFrom(int);
+ field public final boolean allowDestructiveMigrationOnDowngrade;
+ field public final boolean allowMainThreadQueries;
+ field public final java.util.List<androidx.room.RoomDatabase.Callback!>? callbacks;
+ field public final android.content.Context context;
+ field public final String? copyFromAssetPath;
+ field public final java.io.File? copyFromFile;
+ field public final androidx.room.RoomDatabase.JournalMode! journalMode;
+ field public final androidx.room.RoomDatabase.MigrationContainer migrationContainer;
+ field public final boolean multiInstanceInvalidation;
+ field public final String? name;
+ field public final java.util.concurrent.Executor queryExecutor;
+ field public final boolean requireMigration;
+ field public final androidx.sqlite.db.SupportSQLiteOpenHelper.Factory sqliteOpenHelperFactory;
+ field public final java.util.concurrent.Executor transactionExecutor;
+ }
+
+ @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public abstract class EntityDeletionOrUpdateAdapter<T> extends androidx.room.SharedSQLiteStatement {
+ ctor public EntityDeletionOrUpdateAdapter(androidx.room.RoomDatabase!);
+ method protected abstract void bind(androidx.sqlite.db.SupportSQLiteStatement!, T!);
+ method public final int handle(T!);
+ method public final int handleMultiple(Iterable<? extends T>!);
+ method public final int handleMultiple(T![]!);
+ }
+
+ @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public abstract class EntityInsertionAdapter<T> extends androidx.room.SharedSQLiteStatement {
+ ctor public EntityInsertionAdapter(androidx.room.RoomDatabase!);
+ method protected abstract void bind(androidx.sqlite.db.SupportSQLiteStatement!, T!);
+ method public final void insert(T!);
+ method public final void insert(T![]!);
+ method public final void insert(Iterable<? extends T>!);
+ method public final long insertAndReturnId(T!);
+ method public final long[]! insertAndReturnIdsArray(java.util.Collection<? extends T>!);
+ method public final long[]! insertAndReturnIdsArray(T![]!);
+ method public final Long![]! insertAndReturnIdsArrayBox(java.util.Collection<? extends T>!);
+ method public final Long![]! insertAndReturnIdsArrayBox(T![]!);
+ method public final java.util.List<java.lang.Long!>! insertAndReturnIdsList(T![]!);
+ method public final java.util.List<java.lang.Long!>! insertAndReturnIdsList(java.util.Collection<? extends T>!);
+ }
+
+ public class InvalidationTracker {
+ ctor @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public InvalidationTracker(androidx.room.RoomDatabase!, java.lang.String!...);
+ ctor @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public InvalidationTracker(androidx.room.RoomDatabase!, java.util.Map<java.lang.String!,java.lang.String!>!, java.util.Map<java.lang.String!,java.util.Set<java.lang.String!>!>!, java.lang.String!...);
+ method @WorkerThread public void addObserver(androidx.room.InvalidationTracker.Observer);
+ method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public void addWeakObserver(androidx.room.InvalidationTracker.Observer!);
+ method @Deprecated @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public <T> androidx.lifecycle.LiveData<T!>! createLiveData(String![]!, java.util.concurrent.Callable<T!>!);
+ method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public <T> androidx.lifecycle.LiveData<T!>! createLiveData(String![]!, boolean, java.util.concurrent.Callable<T!>!);
+ method public void refreshVersionsAsync();
+ method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) @WorkerThread public void refreshVersionsSync();
+ method @WorkerThread public void removeObserver(androidx.room.InvalidationTracker.Observer);
+ }
+
+ public abstract static class InvalidationTracker.Observer {
+ ctor protected InvalidationTracker.Observer(String, java.lang.String!...);
+ ctor public InvalidationTracker.Observer(String![]);
+ method public abstract void onInvalidated(java.util.Set<java.lang.String!>);
+ }
+
+ @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public class MultiInstanceInvalidationService extends android.app.Service {
+ ctor public MultiInstanceInvalidationService();
+ method public android.os.IBinder? onBind(android.content.Intent!);
+ }
+
+ public class Room {
+ ctor @Deprecated public Room();
+ method public static <T extends androidx.room.RoomDatabase> androidx.room.RoomDatabase.Builder<T!> databaseBuilder(android.content.Context, Class<T!>, String);
+ method public static <T extends androidx.room.RoomDatabase> androidx.room.RoomDatabase.Builder<T!> inMemoryDatabaseBuilder(android.content.Context, Class<T!>);
+ field public static final String MASTER_TABLE_NAME = "room_master_table";
+ }
+
+ public abstract class RoomDatabase {
+ ctor public RoomDatabase();
+ method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public void assertNotMainThread();
+ method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP) public void assertNotSuspendingTransaction();
+ method @Deprecated public void beginTransaction();
+ method @WorkerThread public abstract void clearAllTables();
+ method public void close();
+ method public androidx.sqlite.db.SupportSQLiteStatement! compileStatement(String);
+ method protected abstract androidx.room.InvalidationTracker createInvalidationTracker();
+ method protected abstract androidx.sqlite.db.SupportSQLiteOpenHelper createOpenHelper(androidx.room.DatabaseConfiguration!);
+ method @Deprecated public void endTransaction();
+ method public androidx.room.InvalidationTracker getInvalidationTracker();
+ method public androidx.sqlite.db.SupportSQLiteOpenHelper getOpenHelper();
+ method public java.util.concurrent.Executor getQueryExecutor();
+ method public java.util.concurrent.Executor getTransactionExecutor();
+ method public boolean inTransaction();
+ method @CallSuper public void init(androidx.room.DatabaseConfiguration);
+ method protected void internalInitInvalidationTracker(androidx.sqlite.db.SupportSQLiteDatabase);
+ method public boolean isOpen();
+ method public android.database.Cursor query(String, Object![]?);
+ method public android.database.Cursor query(androidx.sqlite.db.SupportSQLiteQuery);
+ method public android.database.Cursor query(androidx.sqlite.db.SupportSQLiteQuery, android.os.CancellationSignal?);
+ method public void runInTransaction(Runnable);
+ method public <V> V! runInTransaction(java.util.concurrent.Callable<V!>);
+ method @Deprecated public void setTransactionSuccessful();
+ field @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public static final int MAX_BIND_PARAMETER_CNT = 999; // 0x3e7
+ field @Deprecated protected java.util.List<androidx.room.RoomDatabase.Callback!>? mCallbacks;
+ field @Deprecated protected volatile androidx.sqlite.db.SupportSQLiteDatabase! mDatabase;
+ }
+
+ public static class RoomDatabase.Builder<T extends androidx.room.RoomDatabase> {
+ method public androidx.room.RoomDatabase.Builder<T!> addCallback(androidx.room.RoomDatabase.Callback);
+ method public androidx.room.RoomDatabase.Builder<T!> addMigrations(androidx.room.migration.Migration!...);
+ method public androidx.room.RoomDatabase.Builder<T!> allowMainThreadQueries();
+ method public T build();
+ method public androidx.room.RoomDatabase.Builder<T!> createFromAsset(String);
+ method public androidx.room.RoomDatabase.Builder<T!> createFromFile(java.io.File);
+ method public androidx.room.RoomDatabase.Builder<T!> enableMultiInstanceInvalidation();
+ method public androidx.room.RoomDatabase.Builder<T!> fallbackToDestructiveMigration();
+ method public androidx.room.RoomDatabase.Builder<T!> fallbackToDestructiveMigrationFrom(int...);
+ method public androidx.room.RoomDatabase.Builder<T!> fallbackToDestructiveMigrationOnDowngrade();
+ method public androidx.room.RoomDatabase.Builder<T!> openHelperFactory(androidx.sqlite.db.SupportSQLiteOpenHelper.Factory?);
+ method public androidx.room.RoomDatabase.Builder<T!> setJournalMode(androidx.room.RoomDatabase.JournalMode);
+ method public androidx.room.RoomDatabase.Builder<T!> setQueryExecutor(java.util.concurrent.Executor);
+ method public androidx.room.RoomDatabase.Builder<T!> setTransactionExecutor(java.util.concurrent.Executor);
+ }
+
+ public abstract static class RoomDatabase.Callback {
+ ctor public RoomDatabase.Callback();
+ method public void onCreate(androidx.sqlite.db.SupportSQLiteDatabase);
+ method public void onDestructiveMigration(androidx.sqlite.db.SupportSQLiteDatabase);
+ method public void onOpen(androidx.sqlite.db.SupportSQLiteDatabase);
+ }
+
+ public enum RoomDatabase.JournalMode {
+ enum_constant public static final androidx.room.RoomDatabase.JournalMode AUTOMATIC;
+ enum_constant public static final androidx.room.RoomDatabase.JournalMode TRUNCATE;
+ enum_constant @RequiresApi(android.os.Build.VERSION_CODES.JELLY_BEAN) public static final androidx.room.RoomDatabase.JournalMode WRITE_AHEAD_LOGGING;
+ }
+
+ public static class RoomDatabase.MigrationContainer {
+ ctor public RoomDatabase.MigrationContainer();
+ method public void addMigrations(androidx.room.migration.Migration!...);
+ method public java.util.List<androidx.room.migration.Migration!>? findMigrationPath(int, int);
+ }
+
+ @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public class RoomOpenHelper extends androidx.sqlite.db.SupportSQLiteOpenHelper.Callback {
+ ctor public RoomOpenHelper(androidx.room.DatabaseConfiguration, androidx.room.RoomOpenHelper.Delegate, String, String);
+ ctor public RoomOpenHelper(androidx.room.DatabaseConfiguration, androidx.room.RoomOpenHelper.Delegate, String);
+ method public void onCreate(androidx.sqlite.db.SupportSQLiteDatabase!);
+ method public void onUpgrade(androidx.sqlite.db.SupportSQLiteDatabase!, int, int);
+ }
+
+ @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public abstract static class RoomOpenHelper.Delegate {
+ ctor public RoomOpenHelper.Delegate(int);
+ method protected abstract void createAllTables(androidx.sqlite.db.SupportSQLiteDatabase!);
+ method protected abstract void dropAllTables(androidx.sqlite.db.SupportSQLiteDatabase!);
+ method protected abstract void onCreate(androidx.sqlite.db.SupportSQLiteDatabase!);
+ method protected abstract void onOpen(androidx.sqlite.db.SupportSQLiteDatabase!);
+ method protected void onPostMigrate(androidx.sqlite.db.SupportSQLiteDatabase!);
+ method protected void onPreMigrate(androidx.sqlite.db.SupportSQLiteDatabase!);
+ method protected androidx.room.RoomOpenHelper.ValidationResult onValidateSchema(androidx.sqlite.db.SupportSQLiteDatabase);
+ method @Deprecated protected void validateMigration(androidx.sqlite.db.SupportSQLiteDatabase!);
+ field public final int version;
+ }
+
+ @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public static class RoomOpenHelper.ValidationResult {
+ ctor public RoomOpenHelper.ValidationResult(boolean, String?);
+ field public final String? expectedFoundMsg;
+ field public final boolean isValid;
+ }
+
+ @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public class RoomSQLiteQuery implements androidx.sqlite.db.SupportSQLiteProgram androidx.sqlite.db.SupportSQLiteQuery {
+ method public static androidx.room.RoomSQLiteQuery! acquire(String!, int);
+ method public void bindBlob(int, byte[]!);
+ method public void bindDouble(int, double);
+ method public void bindLong(int, long);
+ method public void bindNull(int);
+ method public void bindString(int, String!);
+ method public void bindTo(androidx.sqlite.db.SupportSQLiteProgram!);
+ method public void clearBindings();
+ method public void close();
+ method public void copyArgumentsFrom(androidx.room.RoomSQLiteQuery!);
+ method public static androidx.room.RoomSQLiteQuery! copyFrom(androidx.sqlite.db.SupportSQLiteQuery!);
+ method public int getArgCount();
+ method public String! getSql();
+ method public void release();
+ }
+
+ @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public abstract class SharedSQLiteStatement {
+ ctor public SharedSQLiteStatement(androidx.room.RoomDatabase!);
+ method public androidx.sqlite.db.SupportSQLiteStatement! acquire();
+ method protected void assertNotMainThread();
+ method protected abstract String! createQuery();
+ method public void release(androidx.sqlite.db.SupportSQLiteStatement!);
+ }
+
+}
+
+package androidx.room.migration {
+
+ public abstract class Migration {
+ ctor public Migration(int, int);
+ method public abstract void migrate(androidx.sqlite.db.SupportSQLiteDatabase);
+ field public final int endVersion;
+ field public final int startVersion;
+ }
+
+}
+
+package androidx.room.paging {
+
+ @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public abstract class LimitOffsetDataSource<T> extends androidx.paging.PositionalDataSource<T> {
+ ctor protected LimitOffsetDataSource(androidx.room.RoomDatabase!, androidx.sqlite.db.SupportSQLiteQuery!, boolean, java.lang.String!...);
+ ctor protected LimitOffsetDataSource(androidx.room.RoomDatabase!, androidx.room.RoomSQLiteQuery!, boolean, java.lang.String!...);
+ method protected abstract java.util.List<T!>! convertRows(android.database.Cursor!);
+ method public void loadInitial(androidx.paging.PositionalDataSource.LoadInitialParams, androidx.paging.PositionalDataSource.LoadInitialCallback<T!>);
+ method public void loadRange(androidx.paging.PositionalDataSource.LoadRangeParams, androidx.paging.PositionalDataSource.LoadRangeCallback<T!>);
+ }
+
+}
+
+package androidx.room.util {
+
+ @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public class CopyLock {
+ ctor public CopyLock(String, java.io.File, boolean);
+ method public void lock();
+ method public void unlock();
+ }
+
+ @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public class CursorUtil {
+ method public static android.database.Cursor copyAndClose(android.database.Cursor);
+ method public static int getColumnIndex(android.database.Cursor, String);
+ method public static int getColumnIndexOrThrow(android.database.Cursor, String);
+ }
+
+ @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public class DBUtil {
+ method public static android.os.CancellationSignal? createCancellationSignal();
+ method public static void dropFtsSyncTriggers(androidx.sqlite.db.SupportSQLiteDatabase!);
+ method @Deprecated public static android.database.Cursor query(androidx.room.RoomDatabase!, androidx.sqlite.db.SupportSQLiteQuery!, boolean);
+ method public static android.database.Cursor query(androidx.room.RoomDatabase, androidx.sqlite.db.SupportSQLiteQuery, boolean, android.os.CancellationSignal?);
+ method public static int readVersion(java.io.File) throws java.io.IOException;
+ }
+
+ @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public class FileUtil {
+ method public static void copy(java.nio.channels.ReadableByteChannel, java.nio.channels.FileChannel) throws java.io.IOException;
+ }
+
+ @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public class FtsTableInfo {
+ ctor public FtsTableInfo(String!, java.util.Set<java.lang.String!>!, java.util.Set<java.lang.String!>!);
+ ctor public FtsTableInfo(String!, java.util.Set<java.lang.String!>!, String!);
+ method public static androidx.room.util.FtsTableInfo! read(androidx.sqlite.db.SupportSQLiteDatabase!, String!);
+ field public final java.util.Set<java.lang.String!>! columns;
+ field public final String! name;
+ field public final java.util.Set<java.lang.String!>! options;
+ }
+
+ @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP) public class SneakyThrow {
+ method public static void reThrow(Exception);
+ }
+
+ @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public class StringUtil {
+ method public static void appendPlaceholders(StringBuilder!, int);
+ method public static String? joinIntoString(java.util.List<java.lang.Integer!>?);
+ method public static StringBuilder! newStringBuilder();
+ method public static java.util.List<java.lang.Integer!>? splitToIntList(String?);
+ field public static final String![]! EMPTY_STRING_ARRAY;
+ }
+
+ @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public class TableInfo {
+ ctor public TableInfo(String!, java.util.Map<java.lang.String!,androidx.room.util.TableInfo.Column!>!, java.util.Set<androidx.room.util.TableInfo.ForeignKey!>!, java.util.Set<androidx.room.util.TableInfo.Index!>!);
+ ctor public TableInfo(String!, java.util.Map<java.lang.String!,androidx.room.util.TableInfo.Column!>!, java.util.Set<androidx.room.util.TableInfo.ForeignKey!>!);
+ method public static androidx.room.util.TableInfo! read(androidx.sqlite.db.SupportSQLiteDatabase!, String!);
+ field public static final int CREATED_FROM_DATABASE = 2; // 0x2
+ field public static final int CREATED_FROM_ENTITY = 1; // 0x1
+ field public static final int CREATED_FROM_UNKNOWN = 0; // 0x0
+ field public final java.util.Map<java.lang.String!,androidx.room.util.TableInfo.Column!>! columns;
+ field public final java.util.Set<androidx.room.util.TableInfo.ForeignKey!>! foreignKeys;
+ field public final java.util.Set<androidx.room.util.TableInfo.Index!>? indices;
+ field public final String! name;
+ }
+
+ public static class TableInfo.Column {
+ ctor @Deprecated public TableInfo.Column(String!, String!, boolean, int);
+ ctor public TableInfo.Column(String!, String!, boolean, int, String!, int);
+ method public boolean isPrimaryKey();
+ field public final int affinity;
+ field public final String! defaultValue;
+ field public final String! name;
+ field public final boolean notNull;
+ field public final int primaryKeyPosition;
+ field public final String! type;
+ }
+
+ @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public static class TableInfo.ForeignKey {
+ ctor public TableInfo.ForeignKey(String, String, String, java.util.List<java.lang.String!>, java.util.List<java.lang.String!>);
+ field public final java.util.List<java.lang.String!> columnNames;
+ field public final String onDelete;
+ field public final String onUpdate;
+ field public final java.util.List<java.lang.String!> referenceColumnNames;
+ field public final String referenceTable;
+ }
+
+ @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public static class TableInfo.Index {
+ ctor public TableInfo.Index(String!, boolean, java.util.List<java.lang.String!>!);
+ field public static final String DEFAULT_PREFIX = "index_";
+ field public final java.util.List<java.lang.String!>! columns;
+ field public final String! name;
+ field public final boolean unique;
+ }
+
+ @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public class ViewInfo {
+ ctor public ViewInfo(String!, String!);
+ method public static androidx.room.util.ViewInfo! read(androidx.sqlite.db.SupportSQLiteDatabase!, String!);
+ field public final String! name;
+ field public final String! sql;
+ }
+
+}
+
diff --git a/room/rxjava2/api/2.2.0-beta01.txt b/room/rxjava2/api/2.2.0-beta01.txt
new file mode 100644
index 0000000..07ab5f2
--- /dev/null
+++ b/room/rxjava2/api/2.2.0-beta01.txt
@@ -0,0 +1,16 @@
+// Signature format: 3.0
+package androidx.room {
+
+ public class EmptyResultSetException extends java.lang.RuntimeException {
+ ctor public EmptyResultSetException(String!);
+ }
+
+ public class RxRoom {
+ ctor @Deprecated public RxRoom();
+ method public static io.reactivex.Flowable<java.lang.Object!>! createFlowable(androidx.room.RoomDatabase!, java.lang.String!...);
+ method public static io.reactivex.Observable<java.lang.Object!>! createObservable(androidx.room.RoomDatabase!, java.lang.String!...);
+ field public static final Object! NOTHING;
+ }
+
+}
+
diff --git a/biometric/api/res-1.0.0-beta01.txt b/room/rxjava2/api/res-2.2.0-beta01.txt
similarity index 100%
copy from biometric/api/res-1.0.0-beta01.txt
copy to room/rxjava2/api/res-2.2.0-beta01.txt
diff --git a/room/rxjava2/api/restricted_2.2.0-beta01.txt b/room/rxjava2/api/restricted_2.2.0-beta01.txt
new file mode 100644
index 0000000..869770f
--- /dev/null
+++ b/room/rxjava2/api/restricted_2.2.0-beta01.txt
@@ -0,0 +1,21 @@
+// Signature format: 3.0
+package androidx.room {
+
+ public class EmptyResultSetException extends java.lang.RuntimeException {
+ ctor public EmptyResultSetException(String!);
+ }
+
+ public class RxRoom {
+ ctor @Deprecated public RxRoom();
+ method public static io.reactivex.Flowable<java.lang.Object!>! createFlowable(androidx.room.RoomDatabase!, java.lang.String!...);
+ method @Deprecated @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public static <T> io.reactivex.Flowable<T!>! createFlowable(androidx.room.RoomDatabase!, String![]!, java.util.concurrent.Callable<T!>!);
+ method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public static <T> io.reactivex.Flowable<T!>! createFlowable(androidx.room.RoomDatabase!, boolean, String![]!, java.util.concurrent.Callable<T!>!);
+ method public static io.reactivex.Observable<java.lang.Object!>! createObservable(androidx.room.RoomDatabase!, java.lang.String!...);
+ method @Deprecated @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public static <T> io.reactivex.Observable<T!>! createObservable(androidx.room.RoomDatabase!, String![]!, java.util.concurrent.Callable<T!>!);
+ method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public static <T> io.reactivex.Observable<T!>! createObservable(androidx.room.RoomDatabase!, boolean, String![]!, java.util.concurrent.Callable<T!>!);
+ method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public static <T> io.reactivex.Single<T!>! createSingle(java.util.concurrent.Callable<T!>!);
+ field public static final Object! NOTHING;
+ }
+
+}
+
diff --git a/room/testing/api/2.2.0-beta01.txt b/room/testing/api/2.2.0-beta01.txt
new file mode 100644
index 0000000..86e22e8
--- /dev/null
+++ b/room/testing/api/2.2.0-beta01.txt
@@ -0,0 +1,14 @@
+// Signature format: 3.0
+package androidx.room.testing {
+
+ public class MigrationTestHelper extends org.junit.rules.TestWatcher {
+ ctor public MigrationTestHelper(android.app.Instrumentation!, String!);
+ ctor public MigrationTestHelper(android.app.Instrumentation!, String!, androidx.sqlite.db.SupportSQLiteOpenHelper.Factory!);
+ method public void closeWhenFinished(androidx.sqlite.db.SupportSQLiteDatabase!);
+ method public void closeWhenFinished(androidx.room.RoomDatabase!);
+ method public androidx.sqlite.db.SupportSQLiteDatabase! createDatabase(String!, int) throws java.io.IOException;
+ method public androidx.sqlite.db.SupportSQLiteDatabase! runMigrationsAndValidate(String!, int, boolean, androidx.room.migration.Migration!...) throws java.io.IOException;
+ }
+
+}
+
diff --git a/biometric/api/res-1.0.0-beta01.txt b/room/testing/api/res-2.2.0-beta01.txt
similarity index 100%
copy from biometric/api/res-1.0.0-beta01.txt
copy to room/testing/api/res-2.2.0-beta01.txt
diff --git a/room/testing/api/restricted_2.2.0-beta01.txt b/room/testing/api/restricted_2.2.0-beta01.txt
new file mode 100644
index 0000000..86e22e8
--- /dev/null
+++ b/room/testing/api/restricted_2.2.0-beta01.txt
@@ -0,0 +1,14 @@
+// Signature format: 3.0
+package androidx.room.testing {
+
+ public class MigrationTestHelper extends org.junit.rules.TestWatcher {
+ ctor public MigrationTestHelper(android.app.Instrumentation!, String!);
+ ctor public MigrationTestHelper(android.app.Instrumentation!, String!, androidx.sqlite.db.SupportSQLiteOpenHelper.Factory!);
+ method public void closeWhenFinished(androidx.sqlite.db.SupportSQLiteDatabase!);
+ method public void closeWhenFinished(androidx.room.RoomDatabase!);
+ method public androidx.sqlite.db.SupportSQLiteDatabase! createDatabase(String!, int) throws java.io.IOException;
+ method public androidx.sqlite.db.SupportSQLiteDatabase! runMigrationsAndValidate(String!, int, boolean, androidx.room.migration.Migration!...) throws java.io.IOException;
+ }
+
+}
+
diff --git a/samples/BiometricDemos/src/main/java/com/example/android/biometric/BiometricPromptDemo.java b/samples/BiometricDemos/src/main/java/com/example/android/biometric/BiometricPromptDemo.java
index dbddf77..a73d222 100644
--- a/samples/BiometricDemos/src/main/java/com/example/android/biometric/BiometricPromptDemo.java
+++ b/samples/BiometricDemos/src/main/java/com/example/android/biometric/BiometricPromptDemo.java
@@ -47,33 +47,30 @@
hostInFragmentButton.setOnClickListener(view -> launchFragmentHost());
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
biometricBoundKeyDemoButton.setOnClickListener(view -> launchBiometricBoundKeyDemo());
- } else {
- biometricBoundKeyDemoButton.setVisibility(View.GONE);
- }
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
credentialBoundKeyDemoButton.setOnClickListener(view -> launchCredentialBoundKeyDemo());
} else {
+ biometricBoundKeyDemoButton.setVisibility(View.GONE);
credentialBoundKeyDemoButton.setVisibility(View.GONE);
}
}
private void launchActivityHost() {
- Intent intent = new Intent(this, BiometricPromptDemoActivityHost.class);
+ final Intent intent = new Intent(this, BiometricPromptDemoActivityHost.class);
startActivity(intent);
}
private void launchFragmentHost() {
- Intent intent = new Intent(this, BiometricPromptDemoFragmentHostActivity.class);
+ final Intent intent = new Intent(this, BiometricPromptDemoFragmentHostActivity.class);
startActivity(intent);
}
private void launchBiometricBoundKeyDemo() {
- Intent intent = new Intent(this, BiometricPromptDemoBiometricBoundKeyActivity.class);
+ final Intent intent = new Intent(this, BiometricPromptDemoBiometricBoundKeyActivity.class);
startActivity(intent);
}
private void launchCredentialBoundKeyDemo() {
- Intent intent = new Intent(this, BiometricPromptDemoCredentialBoundKeyActivity.class);
+ final Intent intent = new Intent(this, BiometricPromptDemoCredentialBoundKeyActivity.class);
startActivity(intent);
}
}
diff --git a/samples/BiometricDemos/src/main/java/com/example/android/biometric/BiometricPromptDemoController.java b/samples/BiometricDemos/src/main/java/com/example/android/biometric/BiometricPromptDemoController.java
index f14163a..ae47efb 100644
--- a/samples/BiometricDemos/src/main/java/com/example/android/biometric/BiometricPromptDemoController.java
+++ b/samples/BiometricDemos/src/main/java/com/example/android/biometric/BiometricPromptDemoController.java
@@ -17,7 +17,6 @@
package com.example.android.biometric;
import android.content.Context;
-import android.os.Build;
import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
@@ -162,12 +161,6 @@
}
log("canAuthenticate: " + message);
});
-
-
- if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) {
- mAllowDeviceCredentialCheckbox.setEnabled(false);
- mAllowDeviceCredentialCheckbox.setChecked(false);
- }
}
void onPause() {
@@ -196,8 +189,7 @@
+ mCounter)
.setConfirmationRequired(mRequireConfirmationCheckbox.isChecked());
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q
- && mAllowDeviceCredentialCheckbox.isChecked()) {
+ if (mAllowDeviceCredentialCheckbox.isChecked()) {
builder.setDeviceCredentialAllowed(true);
} else {
builder.setNegativeButtonText("Negative Button " + mCounter);
diff --git a/samples/BiometricDemos/src/main/java/com/example/android/biometric/BiometricPromptDemoCredentialBoundKeyActivity.java b/samples/BiometricDemos/src/main/java/com/example/android/biometric/BiometricPromptDemoCredentialBoundKeyActivity.java
index 6e4dd92..b475450 100644
--- a/samples/BiometricDemos/src/main/java/com/example/android/biometric/BiometricPromptDemoCredentialBoundKeyActivity.java
+++ b/samples/BiometricDemos/src/main/java/com/example/android/biometric/BiometricPromptDemoCredentialBoundKeyActivity.java
@@ -25,6 +25,7 @@
import android.widget.TextView;
import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
import androidx.annotation.RequiresApi;
import androidx.biometric.BiometricPrompt;
import androidx.fragment.app.FragmentActivity;
@@ -50,7 +51,7 @@
/**
* Demo activity that shows how BiometricPrompt can be used with credential bound secret keys.
*/
-@RequiresApi(api = Build.VERSION_CODES.Q)
+@RequiresApi(api = Build.VERSION_CODES.M)
public class BiometricPromptDemoCredentialBoundKeyActivity extends FragmentActivity {
private static final String TAG = "bio_prompt_demo_control";
@@ -150,7 +151,7 @@
};
@Override
- protected void onCreate(Bundle savedInstanceState) {
+ protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.biometric_prompt_demo_credential_bound_key);
diff --git a/samples/SupportCarDemos/src/main/java/com/example/androidx/car/AlphaJumpActivity.java b/samples/SupportCarDemos/src/main/java/com/example/androidx/car/AlphaJumpActivity.java
index 8ad42a5..3917c9f 100644
--- a/samples/SupportCarDemos/src/main/java/com/example/androidx/car/AlphaJumpActivity.java
+++ b/samples/SupportCarDemos/src/main/java/com/example/androidx/car/AlphaJumpActivity.java
@@ -83,7 +83,9 @@
toolbar.setNavigationIconOnClickListener(v -> onNavigateUp());
PagedListView pagedListView = findViewById(R.id.paged_list_view);
- pagedListView.setAdapter(new CheeseAdapter());
+ CheeseAdapter adapter = new CheeseAdapter();
+ pagedListView.setAdapter(adapter);
+ pagedListView.setAlphaJumpAdapter(adapter);
}
/**
diff --git a/samples/SupportPreferenceDemos/build.gradle b/samples/SupportPreferenceDemos/build.gradle
index fbdb445..bebcee1 100644
--- a/samples/SupportPreferenceDemos/build.gradle
+++ b/samples/SupportPreferenceDemos/build.gradle
@@ -5,8 +5,8 @@
dependencies {
implementation(project(":appcompat"))
+ implementation(project(":preference:preference"))
implementation(project(":recyclerview:recyclerview"))
- implementation(project(":preference"))
implementation(project(":leanback"))
implementation(project(":leanback-preference"))
implementation(project(":car"))
diff --git a/settings.gradle b/settings.gradle
index f8607ad..8d46e66 100644
--- a/settings.gradle
+++ b/settings.gradle
@@ -153,6 +153,7 @@
includeProject(":navigation:navigation-benchmark", "navigation/benchmark")
includeProject(":navigation:navigation-common", "navigation/navigation-common")
includeProject(":navigation:navigation-common-ktx", "navigation/navigation-common-ktx")
+includeProject(":navigation:navigation-dynamic-feature", "navigation/dynamic-feature-navigator")
includeProject(":navigation:navigation-runtime", "navigation/navigation-runtime")
includeProject(":navigation:navigation-runtime-ktx", "navigation/navigation-runtime-ktx")
includeProject(":navigation:navigation-testing", "navigation/navigation-testing")
@@ -173,8 +174,8 @@
includeProject(":palette:palette", "palette/palette")
includeProject(":palette:palette-ktx", "palette/palette-ktx")
includeProject(":percentlayout:percentlayout", "percentlayout/percentlayout")
-includeProject(":preference", "preference")
-includeProject(":preference-ktx", "preference/ktx")
+includeProject(":preference:preference", "preference/preference")
+includeProject(":preference:preference-ktx", "preference/preference-ktx")
includeProject(":print", "print")
includeProject(":recommendation", "recommendation")
includeProject(":recyclerview:recyclerview", "recyclerview/recyclerview")
diff --git a/preference/ktx/api/restricted_1.1.0-alpha06.txt b/sharetarget/api/1.0.0-beta01.txt
similarity index 100%
copy from preference/ktx/api/restricted_1.1.0-alpha06.txt
copy to sharetarget/api/1.0.0-beta01.txt
diff --git a/biometric/api/res-1.0.0-beta01.txt b/sharetarget/api/res-1.0.0-beta01.txt
similarity index 100%
copy from biometric/api/res-1.0.0-beta01.txt
copy to sharetarget/api/res-1.0.0-beta01.txt
diff --git a/sharetarget/api/restricted_1.0.0-beta01.txt b/sharetarget/api/restricted_1.0.0-beta01.txt
new file mode 100644
index 0000000..b838cc7
--- /dev/null
+++ b/sharetarget/api/restricted_1.0.0-beta01.txt
@@ -0,0 +1,18 @@
+// Signature format: 3.0
+package androidx.sharetarget {
+
+ @RequiresApi(23) @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public class ChooserTargetServiceCompat extends android.service.chooser.ChooserTargetService {
+ ctor public ChooserTargetServiceCompat();
+ method public java.util.List<android.service.chooser.ChooserTarget!>! onGetChooserTargets(android.content.ComponentName!, android.content.IntentFilter!);
+ }
+
+ @RequiresApi(19) @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public class ShortcutInfoCompatSaverImpl extends androidx.core.content.pm.ShortcutInfoCompatSaver<com.google.common.util.concurrent.ListenableFuture<java.lang.Void>> {
+ method @AnyThread public com.google.common.util.concurrent.ListenableFuture<java.lang.Void!>! addShortcuts(java.util.List<androidx.core.content.pm.ShortcutInfoCompat!>!);
+ method @AnyThread public static androidx.sharetarget.ShortcutInfoCompatSaverImpl! getInstance(android.content.Context!);
+ method @WorkerThread public androidx.core.graphics.drawable.IconCompat! getShortcutIcon(String!) throws java.lang.Exception;
+ method @AnyThread public com.google.common.util.concurrent.ListenableFuture<java.lang.Void!>! removeAllShortcuts();
+ method @AnyThread public com.google.common.util.concurrent.ListenableFuture<java.lang.Void!>! removeShortcuts(java.util.List<java.lang.String!>!);
+ }
+
+}
+
diff --git a/sharetarget/build.gradle b/sharetarget/build.gradle
index b750a70..2f35e82 100644
--- a/sharetarget/build.gradle
+++ b/sharetarget/build.gradle
@@ -25,10 +25,10 @@
}
dependencies {
- api("androidx.core:core:1.1.0-rc01")
+ api("androidx.core:core:1.1.0")
implementation("androidx.collection:collection:1.0.0")
api(GUAVA_LISTENABLE_FUTURE)
- implementation("androidx.concurrent:concurrent-futures:1.0.0-alpha02")
+ implementation("androidx.concurrent:concurrent-futures:1.0.0-beta01")
androidTestImplementation(ANDROIDX_TEST_EXT_JUNIT)
androidTestImplementation(ANDROIDX_TEST_CORE)
diff --git a/sharetarget/integration-tests/testapp/build.gradle b/sharetarget/integration-tests/testapp/build.gradle
index 4cf7a1f..fd2075d 100644
--- a/sharetarget/integration-tests/testapp/build.gradle
+++ b/sharetarget/integration-tests/testapp/build.gradle
@@ -22,8 +22,8 @@
}
dependencies {
- api(project(":core:core"))
- api(project(":sharetarget"))
+ api("androidx.core:core:1.1.0")
+ api("androidx.sharetarget:sharetarget:1.0.0-alpha02")
api(project(":appcompat"))
api(CONSTRAINT_LAYOUT, { transitive = true })
}
diff --git a/slices/builders/build.gradle b/slices/builders/build.gradle
index 914633c..63bbe8d 100644
--- a/slices/builders/build.gradle
+++ b/slices/builders/build.gradle
@@ -28,7 +28,7 @@
implementation(project(":slice-core"))
api(project(":remotecallback"))
implementation "androidx.annotation:annotation:1.1.0"
- implementation "androidx.core:core:1.1.0-rc01"
+ implementation "androidx.core:core:1.1.0"
implementation project(':collection:collection')
}
diff --git a/slices/core/api/1.1.0-alpha02.txt b/slices/core/api/1.1.0-alpha02.txt
index bada082..728a150 100644
--- a/slices/core/api/1.1.0-alpha02.txt
+++ b/slices/core/api/1.1.0-alpha02.txt
@@ -64,7 +64,7 @@
public abstract class SliceProviderWithCallbacks<T extends androidx.slice.SliceProviderWithCallbacks> extends androidx.slice.SliceProvider implements androidx.remotecallback.CallbackBase<T> androidx.remotecallback.CallbackReceiver<T> {
ctor public SliceProviderWithCallbacks();
method public T createRemoteCallback(android.content.Context);
- method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public androidx.remotecallback.RemoteCallback toRemoteCallback(Class<T!>, android.content.Context, String?, android.os.Bundle, String?);
+ method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public androidx.remotecallback.RemoteCallback toRemoteCallback(Class<T!>, android.content.Context, String, android.os.Bundle, String?);
}
}
diff --git a/slices/core/api/current.txt b/slices/core/api/current.txt
index bada082..728a150 100644
--- a/slices/core/api/current.txt
+++ b/slices/core/api/current.txt
@@ -64,7 +64,7 @@
public abstract class SliceProviderWithCallbacks<T extends androidx.slice.SliceProviderWithCallbacks> extends androidx.slice.SliceProvider implements androidx.remotecallback.CallbackBase<T> androidx.remotecallback.CallbackReceiver<T> {
ctor public SliceProviderWithCallbacks();
method public T createRemoteCallback(android.content.Context);
- method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public androidx.remotecallback.RemoteCallback toRemoteCallback(Class<T!>, android.content.Context, String?, android.os.Bundle, String?);
+ method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public androidx.remotecallback.RemoteCallback toRemoteCallback(Class<T!>, android.content.Context, String, android.os.Bundle, String?);
}
}
diff --git a/slices/core/api/restricted_1.1.0-alpha02.txt b/slices/core/api/restricted_1.1.0-alpha02.txt
index 3cf1dca..878a544 100644
--- a/slices/core/api/restricted_1.1.0-alpha02.txt
+++ b/slices/core/api/restricted_1.1.0-alpha02.txt
@@ -126,7 +126,7 @@
public abstract class SliceProviderWithCallbacks<T extends androidx.slice.SliceProviderWithCallbacks> extends androidx.slice.SliceProvider implements androidx.remotecallback.CallbackBase<T> androidx.remotecallback.CallbackReceiver<T> {
ctor public SliceProviderWithCallbacks();
method public T createRemoteCallback(android.content.Context);
- method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public androidx.remotecallback.RemoteCallback toRemoteCallback(Class<T!>, android.content.Context, String?, android.os.Bundle, String?);
+ method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public androidx.remotecallback.RemoteCallback toRemoteCallback(Class<T!>, android.content.Context, String, android.os.Bundle, String?);
}
@RequiresApi(19) @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public final class SliceSpec implements androidx.versionedparcelable.VersionedParcelable {
diff --git a/slices/core/api/restricted_current.txt b/slices/core/api/restricted_current.txt
index 3cf1dca..878a544 100644
--- a/slices/core/api/restricted_current.txt
+++ b/slices/core/api/restricted_current.txt
@@ -126,7 +126,7 @@
public abstract class SliceProviderWithCallbacks<T extends androidx.slice.SliceProviderWithCallbacks> extends androidx.slice.SliceProvider implements androidx.remotecallback.CallbackBase<T> androidx.remotecallback.CallbackReceiver<T> {
ctor public SliceProviderWithCallbacks();
method public T createRemoteCallback(android.content.Context);
- method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public androidx.remotecallback.RemoteCallback toRemoteCallback(Class<T!>, android.content.Context, String?, android.os.Bundle, String?);
+ method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public androidx.remotecallback.RemoteCallback toRemoteCallback(Class<T!>, android.content.Context, String, android.os.Bundle, String?);
}
@RequiresApi(19) @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public final class SliceSpec implements androidx.versionedparcelable.VersionedParcelable {
diff --git a/slices/core/src/main/java/androidx/slice/SliceProvider.java b/slices/core/src/main/java/androidx/slice/SliceProvider.java
index a4b57aa..6164547 100644
--- a/slices/core/src/main/java/androidx/slice/SliceProvider.java
+++ b/slices/core/src/main/java/androidx/slice/SliceProvider.java
@@ -235,6 +235,7 @@
@Override
public Bundle call(@NonNull String method, @Nullable String arg, @Nullable Bundle extras) {
if (Build.VERSION.SDK_INT < 19) return null;
+ if (extras == null) return null;
return mCompat != null ? mCompat.call(method, arg, extras) : null;
}
diff --git a/slices/core/src/main/java/androidx/slice/SliceProviderWithCallbacks.java b/slices/core/src/main/java/androidx/slice/SliceProviderWithCallbacks.java
index 5404f79..fffb2d2 100644
--- a/slices/core/src/main/java/androidx/slice/SliceProviderWithCallbacks.java
+++ b/slices/core/src/main/java/androidx/slice/SliceProviderWithCallbacks.java
@@ -59,6 +59,7 @@
@Nullable
@Override
public Bundle call(@NonNull String method, @Nullable String arg, @Nullable Bundle extras) {
+ if (extras == null) return null;
if (ProviderRelayReceiver.METHOD_PROVIDER_CALLBACK.equals(method)) {
CallbackHandlerRegistry.sInstance.invokeCallback(getContext(), this, extras);
return null;
@@ -81,7 +82,7 @@
@Override
@RestrictTo(LIBRARY_GROUP_PREFIX)
public RemoteCallback toRemoteCallback(@NonNull Class<T> cls, @NonNull Context context,
- @Nullable String authority, @NonNull Bundle args, @Nullable String method) {
+ @NonNull String authority, @NonNull Bundle args, @Nullable String method) {
if (authority == null) {
throw new IllegalStateException(
"ContentProvider must be attached before creating callbacks");
diff --git a/slidingpanelayout/build.gradle b/slidingpanelayout/build.gradle
index b9732d5..7738665 100644
--- a/slidingpanelayout/build.gradle
+++ b/slidingpanelayout/build.gradle
@@ -9,7 +9,7 @@
dependencies {
api("androidx.annotation:annotation:1.1.0")
- implementation("androidx.core:core:1.1.0-rc01")
+ implementation("androidx.core:core:1.1.0")
api(project(":customview"))
}
diff --git a/sqlite/OWNERS b/sqlite/OWNERS
index fc51372..edfaa80 100644
--- a/sqlite/OWNERS
+++ b/sqlite/OWNERS
@@ -1,2 +1,3 @@
sergeyv@google.com
-yboyar@google.com
\ No newline at end of file
+yboyar@google.com
+danysantiago@google.com
\ No newline at end of file
diff --git a/swiperefreshlayout/OWNERS b/swiperefreshlayout/OWNERS
new file mode 100644
index 0000000..f92940c
--- /dev/null
+++ b/swiperefreshlayout/OWNERS
@@ -0,0 +1 @@
+shepshapard@google.com
\ No newline at end of file
diff --git a/swiperefreshlayout/build.gradle b/swiperefreshlayout/build.gradle
index 1309851..46c3da4 100644
--- a/swiperefreshlayout/build.gradle
+++ b/swiperefreshlayout/build.gradle
@@ -10,7 +10,7 @@
dependencies {
api("androidx.annotation:annotation:1.1.0")
- api("androidx.core:core:1.1.0-rc01")
+ api("androidx.core:core:1.1.0")
api("androidx.interpolator:interpolator:1.0.0")
androidTestImplementation(JUNIT)
diff --git a/textclassifier/build.gradle b/textclassifier/build.gradle
index d2cd121..92508eb 100644
--- a/textclassifier/build.gradle
+++ b/textclassifier/build.gradle
@@ -13,7 +13,7 @@
api("androidx.annotation:annotation:1.1.0")
implementation("androidx.collection:collection:1.0.0")
// TODO: change to 1.1.0-alpha04 after release
- api("androidx.core:core:1.1.0-rc01")
+ api("androidx.core:core:1.1.0")
androidTestImplementation(ANDROIDX_TEST_EXT_JUNIT)
androidTestImplementation(ANDROIDX_TEST_CORE)
diff --git a/transition/transition/api/1.2.0-rc01.txt b/transition/transition/api/1.2.0-rc01.txt
new file mode 100644
index 0000000..b7f4588
--- /dev/null
+++ b/transition/transition/api/1.2.0-rc01.txt
@@ -0,0 +1,283 @@
+// Signature format: 3.0
+package androidx.transition {
+
+ public class ArcMotion extends androidx.transition.PathMotion {
+ ctor public ArcMotion();
+ ctor public ArcMotion(android.content.Context!, android.util.AttributeSet!);
+ method public float getMaximumAngle();
+ method public float getMinimumHorizontalAngle();
+ method public float getMinimumVerticalAngle();
+ method public android.graphics.Path! getPath(float, float, float, float);
+ method public void setMaximumAngle(float);
+ method public void setMinimumHorizontalAngle(float);
+ method public void setMinimumVerticalAngle(float);
+ }
+
+ public class AutoTransition extends androidx.transition.TransitionSet {
+ ctor public AutoTransition();
+ ctor public AutoTransition(android.content.Context!, android.util.AttributeSet!);
+ }
+
+ public class ChangeBounds extends androidx.transition.Transition {
+ ctor public ChangeBounds();
+ ctor public ChangeBounds(android.content.Context!, android.util.AttributeSet!);
+ method public void captureEndValues(androidx.transition.TransitionValues);
+ method public void captureStartValues(androidx.transition.TransitionValues);
+ method public boolean getResizeClip();
+ method public void setResizeClip(boolean);
+ }
+
+ public class ChangeClipBounds extends androidx.transition.Transition {
+ ctor public ChangeClipBounds();
+ ctor public ChangeClipBounds(android.content.Context!, android.util.AttributeSet!);
+ method public void captureEndValues(androidx.transition.TransitionValues);
+ method public void captureStartValues(androidx.transition.TransitionValues);
+ }
+
+ public class ChangeImageTransform extends androidx.transition.Transition {
+ ctor public ChangeImageTransform();
+ ctor public ChangeImageTransform(android.content.Context!, android.util.AttributeSet!);
+ method public void captureEndValues(androidx.transition.TransitionValues);
+ method public void captureStartValues(androidx.transition.TransitionValues);
+ }
+
+ public class ChangeScroll extends androidx.transition.Transition {
+ ctor public ChangeScroll();
+ ctor public ChangeScroll(android.content.Context!, android.util.AttributeSet!);
+ method public void captureEndValues(androidx.transition.TransitionValues);
+ method public void captureStartValues(androidx.transition.TransitionValues);
+ }
+
+ public class ChangeTransform extends androidx.transition.Transition {
+ ctor public ChangeTransform();
+ ctor public ChangeTransform(android.content.Context!, android.util.AttributeSet!);
+ method public void captureEndValues(androidx.transition.TransitionValues);
+ method public void captureStartValues(androidx.transition.TransitionValues);
+ method public boolean getReparent();
+ method public boolean getReparentWithOverlay();
+ method public void setReparent(boolean);
+ method public void setReparentWithOverlay(boolean);
+ }
+
+ public class CircularPropagation extends androidx.transition.VisibilityPropagation {
+ ctor public CircularPropagation();
+ method public long getStartDelay(android.view.ViewGroup!, androidx.transition.Transition!, androidx.transition.TransitionValues!, androidx.transition.TransitionValues!);
+ method public void setPropagationSpeed(float);
+ }
+
+ public class Explode extends androidx.transition.Visibility {
+ ctor public Explode();
+ ctor public Explode(android.content.Context!, android.util.AttributeSet!);
+ }
+
+ public class Fade extends androidx.transition.Visibility {
+ ctor public Fade(int);
+ ctor public Fade();
+ ctor public Fade(android.content.Context!, android.util.AttributeSet!);
+ field public static final int IN = 1; // 0x1
+ field public static final int OUT = 2; // 0x2
+ }
+
+ public abstract class PathMotion {
+ ctor public PathMotion();
+ ctor public PathMotion(android.content.Context!, android.util.AttributeSet!);
+ method public abstract android.graphics.Path! getPath(float, float, float, float);
+ }
+
+ public class PatternPathMotion extends androidx.transition.PathMotion {
+ ctor public PatternPathMotion();
+ ctor public PatternPathMotion(android.content.Context!, android.util.AttributeSet!);
+ ctor public PatternPathMotion(android.graphics.Path!);
+ method public android.graphics.Path! getPath(float, float, float, float);
+ method public android.graphics.Path! getPatternPath();
+ method public void setPatternPath(android.graphics.Path!);
+ }
+
+ public class Scene {
+ ctor public Scene(android.view.ViewGroup);
+ ctor public Scene(android.view.ViewGroup, android.view.View);
+ method public void enter();
+ method public void exit();
+ method public static androidx.transition.Scene? getCurrentScene(android.view.ViewGroup);
+ method public static androidx.transition.Scene getSceneForLayout(android.view.ViewGroup, @LayoutRes int, android.content.Context);
+ method public android.view.ViewGroup getSceneRoot();
+ method public void setEnterAction(Runnable?);
+ method public void setExitAction(Runnable?);
+ }
+
+ public class SidePropagation extends androidx.transition.VisibilityPropagation {
+ ctor public SidePropagation();
+ method public long getStartDelay(android.view.ViewGroup!, androidx.transition.Transition!, androidx.transition.TransitionValues!, androidx.transition.TransitionValues!);
+ method public void setPropagationSpeed(float);
+ method public void setSide(int);
+ }
+
+ public class Slide extends androidx.transition.Visibility {
+ ctor public Slide();
+ ctor public Slide(int);
+ ctor public Slide(android.content.Context!, android.util.AttributeSet!);
+ method public int getSlideEdge();
+ method public void setSlideEdge(int);
+ }
+
+ public abstract class Transition implements java.lang.Cloneable {
+ ctor public Transition();
+ ctor public Transition(android.content.Context!, android.util.AttributeSet!);
+ method public androidx.transition.Transition addListener(androidx.transition.Transition.TransitionListener);
+ method public androidx.transition.Transition addTarget(android.view.View);
+ method public androidx.transition.Transition addTarget(@IdRes int);
+ method public androidx.transition.Transition addTarget(String);
+ method public androidx.transition.Transition addTarget(Class<?>);
+ method public abstract void captureEndValues(androidx.transition.TransitionValues);
+ method public abstract void captureStartValues(androidx.transition.TransitionValues);
+ method public androidx.transition.Transition! clone();
+ method public android.animation.Animator? createAnimator(android.view.ViewGroup, androidx.transition.TransitionValues?, androidx.transition.TransitionValues?);
+ method public androidx.transition.Transition excludeChildren(android.view.View, boolean);
+ method public androidx.transition.Transition excludeChildren(@IdRes int, boolean);
+ method public androidx.transition.Transition excludeChildren(Class<?>, boolean);
+ method public androidx.transition.Transition excludeTarget(android.view.View, boolean);
+ method public androidx.transition.Transition excludeTarget(@IdRes int, boolean);
+ method public androidx.transition.Transition excludeTarget(String, boolean);
+ method public androidx.transition.Transition excludeTarget(Class<?>, boolean);
+ method public long getDuration();
+ method public android.graphics.Rect? getEpicenter();
+ method public androidx.transition.Transition.EpicenterCallback? getEpicenterCallback();
+ method public android.animation.TimeInterpolator? getInterpolator();
+ method public String getName();
+ method public androidx.transition.PathMotion getPathMotion();
+ method public androidx.transition.TransitionPropagation? getPropagation();
+ method public long getStartDelay();
+ method public java.util.List<java.lang.Integer!> getTargetIds();
+ method public java.util.List<java.lang.String!>? getTargetNames();
+ method public java.util.List<java.lang.Class<?>!>? getTargetTypes();
+ method public java.util.List<android.view.View!> getTargets();
+ method public String![]? getTransitionProperties();
+ method public androidx.transition.TransitionValues? getTransitionValues(android.view.View, boolean);
+ method public boolean isTransitionRequired(androidx.transition.TransitionValues?, androidx.transition.TransitionValues?);
+ method public androidx.transition.Transition removeListener(androidx.transition.Transition.TransitionListener);
+ method public androidx.transition.Transition removeTarget(android.view.View);
+ method public androidx.transition.Transition removeTarget(@IdRes int);
+ method public androidx.transition.Transition removeTarget(String);
+ method public androidx.transition.Transition removeTarget(Class<?>);
+ method public androidx.transition.Transition setDuration(long);
+ method public void setEpicenterCallback(androidx.transition.Transition.EpicenterCallback?);
+ method public androidx.transition.Transition setInterpolator(android.animation.TimeInterpolator?);
+ method public void setMatchOrder(int...);
+ method public void setPathMotion(androidx.transition.PathMotion?);
+ method public void setPropagation(androidx.transition.TransitionPropagation?);
+ method public androidx.transition.Transition setStartDelay(long);
+ field public static final int MATCH_ID = 3; // 0x3
+ field public static final int MATCH_INSTANCE = 1; // 0x1
+ field public static final int MATCH_ITEM_ID = 4; // 0x4
+ field public static final int MATCH_NAME = 2; // 0x2
+ }
+
+ public abstract static class Transition.EpicenterCallback {
+ ctor public Transition.EpicenterCallback();
+ method public abstract android.graphics.Rect! onGetEpicenter(androidx.transition.Transition);
+ }
+
+ public static interface Transition.TransitionListener {
+ method public void onTransitionCancel(androidx.transition.Transition);
+ method public void onTransitionEnd(androidx.transition.Transition);
+ method public void onTransitionPause(androidx.transition.Transition);
+ method public void onTransitionResume(androidx.transition.Transition);
+ method public void onTransitionStart(androidx.transition.Transition);
+ }
+
+ public class TransitionInflater {
+ method public static androidx.transition.TransitionInflater! from(android.content.Context!);
+ method public androidx.transition.Transition! inflateTransition(int);
+ method public androidx.transition.TransitionManager! inflateTransitionManager(int, android.view.ViewGroup!);
+ }
+
+ public class TransitionListenerAdapter implements androidx.transition.Transition.TransitionListener {
+ ctor public TransitionListenerAdapter();
+ method public void onTransitionCancel(androidx.transition.Transition);
+ method public void onTransitionEnd(androidx.transition.Transition);
+ method public void onTransitionPause(androidx.transition.Transition);
+ method public void onTransitionResume(androidx.transition.Transition);
+ method public void onTransitionStart(androidx.transition.Transition);
+ }
+
+ public class TransitionManager {
+ ctor public TransitionManager();
+ method public static void beginDelayedTransition(android.view.ViewGroup);
+ method public static void beginDelayedTransition(android.view.ViewGroup, androidx.transition.Transition?);
+ method public static void endTransitions(android.view.ViewGroup!);
+ method public static void go(androidx.transition.Scene);
+ method public static void go(androidx.transition.Scene, androidx.transition.Transition?);
+ method public void setTransition(androidx.transition.Scene, androidx.transition.Transition?);
+ method public void setTransition(androidx.transition.Scene, androidx.transition.Scene, androidx.transition.Transition?);
+ method public void transitionTo(androidx.transition.Scene);
+ }
+
+ public abstract class TransitionPropagation {
+ ctor public TransitionPropagation();
+ method public abstract void captureValues(androidx.transition.TransitionValues!);
+ method public abstract String![]! getPropagationProperties();
+ method public abstract long getStartDelay(android.view.ViewGroup!, androidx.transition.Transition!, androidx.transition.TransitionValues!, androidx.transition.TransitionValues!);
+ }
+
+ public class TransitionSet extends androidx.transition.Transition {
+ ctor public TransitionSet();
+ ctor public TransitionSet(android.content.Context!, android.util.AttributeSet!);
+ method public androidx.transition.TransitionSet addListener(androidx.transition.Transition.TransitionListener);
+ method public androidx.transition.TransitionSet addTarget(android.view.View);
+ method public androidx.transition.TransitionSet addTarget(@IdRes int);
+ method public androidx.transition.TransitionSet addTarget(String);
+ method public androidx.transition.TransitionSet addTarget(Class<?>);
+ method public androidx.transition.TransitionSet addTransition(androidx.transition.Transition);
+ method public void captureEndValues(androidx.transition.TransitionValues);
+ method public void captureStartValues(androidx.transition.TransitionValues);
+ method public int getOrdering();
+ method public androidx.transition.Transition? getTransitionAt(int);
+ method public int getTransitionCount();
+ method public androidx.transition.TransitionSet removeListener(androidx.transition.Transition.TransitionListener);
+ method public androidx.transition.TransitionSet removeTarget(@IdRes int);
+ method public androidx.transition.TransitionSet removeTarget(android.view.View);
+ method public androidx.transition.TransitionSet removeTarget(Class<?>);
+ method public androidx.transition.TransitionSet removeTarget(String);
+ method public androidx.transition.TransitionSet removeTransition(androidx.transition.Transition);
+ method public androidx.transition.TransitionSet setDuration(long);
+ method public androidx.transition.TransitionSet setInterpolator(android.animation.TimeInterpolator?);
+ method public androidx.transition.TransitionSet setOrdering(int);
+ method public androidx.transition.TransitionSet setStartDelay(long);
+ field public static final int ORDERING_SEQUENTIAL = 1; // 0x1
+ field public static final int ORDERING_TOGETHER = 0; // 0x0
+ }
+
+ public class TransitionValues {
+ ctor @Deprecated public TransitionValues();
+ ctor public TransitionValues(android.view.View);
+ field public final java.util.Map<java.lang.String!,java.lang.Object!>! values;
+ field public android.view.View! view;
+ }
+
+ public abstract class Visibility extends androidx.transition.Transition {
+ ctor public Visibility();
+ ctor public Visibility(android.content.Context!, android.util.AttributeSet!);
+ method public void captureEndValues(androidx.transition.TransitionValues);
+ method public void captureStartValues(androidx.transition.TransitionValues);
+ method public int getMode();
+ method public boolean isVisible(androidx.transition.TransitionValues!);
+ method public android.animation.Animator! onAppear(android.view.ViewGroup!, androidx.transition.TransitionValues!, int, androidx.transition.TransitionValues!, int);
+ method public android.animation.Animator! onAppear(android.view.ViewGroup!, android.view.View!, androidx.transition.TransitionValues!, androidx.transition.TransitionValues!);
+ method public android.animation.Animator! onDisappear(android.view.ViewGroup!, androidx.transition.TransitionValues!, int, androidx.transition.TransitionValues!, int);
+ method public android.animation.Animator! onDisappear(android.view.ViewGroup!, android.view.View!, androidx.transition.TransitionValues!, androidx.transition.TransitionValues!);
+ method public void setMode(int);
+ field public static final int MODE_IN = 1; // 0x1
+ field public static final int MODE_OUT = 2; // 0x2
+ }
+
+ public abstract class VisibilityPropagation extends androidx.transition.TransitionPropagation {
+ ctor public VisibilityPropagation();
+ method public void captureValues(androidx.transition.TransitionValues!);
+ method public String![]! getPropagationProperties();
+ method public int getViewVisibility(androidx.transition.TransitionValues!);
+ method public int getViewX(androidx.transition.TransitionValues!);
+ method public int getViewY(androidx.transition.TransitionValues!);
+ }
+
+}
+
diff --git a/preference/ktx/api/res-1.1.0-rc01.txt b/transition/transition/api/res-1.2.0-rc01.txt
similarity index 100%
copy from preference/ktx/api/res-1.1.0-rc01.txt
copy to transition/transition/api/res-1.2.0-rc01.txt
diff --git a/transition/transition/src/main/java/androidx/transition/ImageViewUtils.java b/transition/transition/src/main/java/androidx/transition/ImageViewUtils.java
index 9332478..0fd145f 100644
--- a/transition/transition/src/main/java/androidx/transition/ImageViewUtils.java
+++ b/transition/transition/src/main/java/androidx/transition/ImageViewUtils.java
@@ -41,7 +41,6 @@
/**
* Sets the matrix to animate the content of the image view.
*/
- @SuppressLint("NewApi") // TODO: Remove this suppression once Q SDK is released.
static void animateTransform(@NonNull ImageView view, @Nullable Matrix matrix) {
if (Build.VERSION.SDK_INT >= 29) {
view.animateTransform(matrix);
diff --git a/transition/transition/src/main/java/androidx/transition/ViewGroupUtils.java b/transition/transition/src/main/java/androidx/transition/ViewGroupUtils.java
index 63bf693..037fe50 100644
--- a/transition/transition/src/main/java/androidx/transition/ViewGroupUtils.java
+++ b/transition/transition/src/main/java/androidx/transition/ViewGroupUtils.java
@@ -52,7 +52,6 @@
/**
* Provides access to the hidden ViewGroup#suppressLayout method.
*/
- @SuppressLint("NewApi") // TODO: Remove this suppression once Q SDK is released.
static void suppressLayout(@NonNull ViewGroup group, boolean suppress) {
if (Build.VERSION.SDK_INT >= 29) {
group.suppressLayout(suppress);
@@ -80,7 +79,6 @@
/**
* Returns the index of the child to draw for this iteration.
*/
- @SuppressLint("NewApi") // TODO: Remove this suppression once Q SDK is released.
static int getChildDrawingOrder(@NonNull ViewGroup viewGroup, int i) {
if (Build.VERSION.SDK_INT >= 29) {
return viewGroup.getChildDrawingOrder(i);
diff --git a/transition/transition/src/main/java/androidx/transition/ViewUtils.java b/transition/transition/src/main/java/androidx/transition/ViewUtils.java
index 566db5e..608aba0 100644
--- a/transition/transition/src/main/java/androidx/transition/ViewUtils.java
+++ b/transition/transition/src/main/java/androidx/transition/ViewUtils.java
@@ -16,7 +16,6 @@
package androidx.transition;
-import android.annotation.SuppressLint;
import android.graphics.Matrix;
import android.graphics.Rect;
import android.os.Build;
@@ -37,9 +36,7 @@
static {
if (Build.VERSION.SDK_INT >= 29) {
- // TODO: replace with 'new ViewUtilsApi29()' when we can use an SDK_INT check as lint
- // doesn't understand BuildCompat API checks
- IMPL = createViewUtilsApi29();
+ IMPL = new ViewUtilsApi29();
} else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
IMPL = new ViewUtilsApi23();
} else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP_MR1) {
@@ -202,12 +199,6 @@
IMPL.setLeftTopRightBottom(v, left, top, right, bottom);
}
- // TODO: delete when we use an SDK_INT check as lint doesn't understand BuildCompat API checks
- @SuppressLint("NewApi")
- private static ViewUtilsApi29 createViewUtilsApi29() {
- return new ViewUtilsApi29();
- }
-
private ViewUtils() {
}
}
diff --git a/tv-provider/build.gradle b/tv-provider/build.gradle
index b4cbc11..902b0d5 100644
--- a/tv-provider/build.gradle
+++ b/tv-provider/build.gradle
@@ -10,7 +10,7 @@
dependencies {
api("androidx.annotation:annotation:1.1.0")
- api("androidx.core:core:1.1.0-rc01")
+ api("androidx.core:core:1.1.0")
androidTestImplementation(ANDROIDX_TEST_EXT_JUNIT)
androidTestImplementation(ANDROIDX_TEST_CORE)
diff --git a/ui/integration-tests/test/src/main/java/androidx/ui/test/cases/NestedScrollerTestCase.kt b/ui/integration-tests/test/src/main/java/androidx/ui/test/cases/NestedScrollerTestCase.kt
index 97af4e8..a975728 100644
--- a/ui/integration-tests/test/src/main/java/androidx/ui/test/cases/NestedScrollerTestCase.kt
+++ b/ui/integration-tests/test/src/main/java/androidx/ui/test/cases/NestedScrollerTestCase.kt
@@ -31,10 +31,10 @@
import androidx.ui.layout.Column
import androidx.ui.layout.CrossAxisAlignment
import androidx.ui.layout.FlexColumn
-import androidx.ui.layout.HorizontalScroller
+import androidx.ui.foundation.HorizontalScroller
import androidx.ui.layout.Row
-import androidx.ui.layout.ScrollerPosition
-import androidx.ui.layout.VerticalScroller
+import androidx.ui.foundation.ScrollerPosition
+import androidx.ui.foundation.VerticalScroller
import androidx.ui.material.MaterialTheme
import androidx.ui.material.surface.Surface
import androidx.ui.test.ComposeTestCase
@@ -65,7 +65,7 @@
}!!
override fun toggleState() {
- scrollerPosition.position = if (scrollerPosition.position == 0.px) 10.px else 0.px
+ scrollerPosition.value = if (scrollerPosition.value == 0.px) 10.px else 0.px
FrameManager.nextFrame()
}
diff --git a/ui/integration-tests/test/src/main/java/androidx/ui/test/cases/ScrollerTestCase.kt b/ui/integration-tests/test/src/main/java/androidx/ui/test/cases/ScrollerTestCase.kt
index 40d97a8..65ac2d2 100644
--- a/ui/integration-tests/test/src/main/java/androidx/ui/test/cases/ScrollerTestCase.kt
+++ b/ui/integration-tests/test/src/main/java/androidx/ui/test/cases/ScrollerTestCase.kt
@@ -32,8 +32,8 @@
import androidx.ui.layout.Column
import androidx.ui.layout.Container
import androidx.ui.layout.CrossAxisAlignment
-import androidx.ui.layout.ScrollerPosition
-import androidx.ui.layout.VerticalScroller
+import androidx.ui.foundation.ScrollerPosition
+import androidx.ui.foundation.VerticalScroller
import androidx.ui.painting.Paint
import androidx.ui.painting.PaintingStyle
import androidx.ui.test.ComposeTestCase
@@ -75,7 +75,7 @@
}!!
override fun toggleState() {
- scrollerPosition.position = if (scrollerPosition.position == 0.px) 10.px else 0.px
+ scrollerPosition.value = if (scrollerPosition.value == 0.px) 10.px else 0.px
FrameManager.nextFrame()
}
diff --git a/ui/ui-android-text/src/main/java/androidx/text/TextLayout.kt b/ui/ui-android-text/src/main/java/androidx/text/TextLayout.kt
index 73709d0..021281d 100644
--- a/ui/ui-android-text/src/main/java/androidx/text/TextLayout.kt
+++ b/ui/ui-android-text/src/main/java/androidx/text/TextLayout.kt
@@ -184,8 +184,14 @@
fun getPrimaryHorizontal(offset: Int): Float = layout.getPrimaryHorizontal(offset)
+ fun getSecondaryHorizontal(offset: Int): Float = layout.getSecondaryHorizontal(offset)
+
fun getLineForOffset(offset: Int): Int = layout.getLineForOffset(offset)
+ fun isRtlCharAt(offset: Int): Boolean = layout.isRtlCharAt(offset)
+
+ fun getParagraphDirection(line: Int): Int = layout.getParagraphDirection(line)
+
fun getSelectionPath(start: Int, end: Int, dest: Path) =
layout.getSelectionPath(start, end, dest)
diff --git a/ui/ui-animation/integration-tests/animation-demos/src/main/java/androidx/ui/animation/demos/FancyScrolling.kt b/ui/ui-animation/integration-tests/animation-demos/src/main/java/androidx/ui/animation/demos/FancyScrolling.kt
index f7d5a45..4c98b42 100644
--- a/ui/ui-animation/integration-tests/animation-demos/src/main/java/androidx/ui/animation/demos/FancyScrolling.kt
+++ b/ui/ui-animation/integration-tests/animation-demos/src/main/java/androidx/ui/animation/demos/FancyScrolling.kt
@@ -100,9 +100,9 @@
drawItems(canvas, scroll, width, parentSize.height.value, paint)
}
}
- Layout(children = children, layoutBlock = { _, constraints ->
+ Layout(children) { _, constraints ->
layout(constraints.maxWidth, IntPx(1200)) {}
- })
+ }
}
}
}
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 ab11074..887043da 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
@@ -46,9 +46,9 @@
@Composable
fun HelloAnimation() {
- Layout(children = { ColorRect() }, layoutBlock = { _, constraints ->
+ Layout(children = { ColorRect() }) { _, constraints ->
layout(constraints.maxWidth, constraints.maxHeight) {}
- })
+ }
}
private val background = ColorPropKey()
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 868f2fa..a3e6399 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
@@ -75,9 +75,9 @@
DrawScaledRect(scale = state[scale], color = state[color])
}
}
- Layout(children = children, layoutBlock = { _, constraints ->
+ Layout(children) { _, constraints ->
layout(constraints.maxWidth, constraints.maxHeight) {}
- })
+ }
}
}
diff --git a/ui/ui-animation/integration-tests/animation-demos/src/main/java/androidx/ui/animation/demos/SpringBackScrolling.kt b/ui/ui-animation/integration-tests/animation-demos/src/main/java/androidx/ui/animation/demos/SpringBackScrolling.kt
index 28fa330..a4f4214 100644
--- a/ui/ui-animation/integration-tests/animation-demos/src/main/java/androidx/ui/animation/demos/SpringBackScrolling.kt
+++ b/ui/ui-animation/integration-tests/animation-demos/src/main/java/androidx/ui/animation/demos/SpringBackScrolling.kt
@@ -110,9 +110,9 @@
drawRects(canvas, parentSize, paint, animScroll.value)
}
}
- Layout(children = children, layoutBlock = { _, constraints ->
+ Layout(children) { _, constraints ->
layout(constraints.maxWidth, IntPx(1200)) {}
- })
+ }
}
}
}
diff --git a/ui/ui-animation/integration-tests/animation-demos/src/main/java/androidx/ui/animation/demos/SwipeToDismiss.kt b/ui/ui-animation/integration-tests/animation-demos/src/main/java/androidx/ui/animation/demos/SwipeToDismiss.kt
index 1155e87..dd8968e0 100644
--- a/ui/ui-animation/integration-tests/animation-demos/src/main/java/androidx/ui/animation/demos/SwipeToDismiss.kt
+++ b/ui/ui-animation/integration-tests/animation-demos/src/main/java/androidx/ui/animation/demos/SwipeToDismiss.kt
@@ -136,9 +136,9 @@
OnChildPositioned({ coordinates ->
itemWidth.value = coordinates.size.width.value * 2 / 3f
}) {
- Layout(children = children, layoutBlock = { _, constraints ->
- layout(constraints.maxWidth, IntPx(height.toInt())) {}
- })
+ Layout(children) { _, constraints ->
+ layout(constraints.maxWidth, IntPx(height.toInt())) {}
+ }
}
}
}
diff --git a/ui/ui-core/api/1.0.0-alpha01.txt b/ui/ui-core/api/1.0.0-alpha01.txt
index bac3029..87217df 100644
--- a/ui/ui-core/api/1.0.0-alpha01.txt
+++ b/ui/ui-core/api/1.0.0-alpha01.txt
@@ -432,6 +432,11 @@
method public androidx.ui.core.IntPx getWidth();
}
+ public enum LayoutDirection {
+ enum_constant public static final androidx.ui.core.LayoutDirection Ltr;
+ enum_constant public static final androidx.ui.core.LayoutDirection Rtl;
+ }
+
public enum PointerEventPass {
enum_constant public static final androidx.ui.core.PointerEventPass InitialDown;
enum_constant public static final androidx.ui.core.PointerEventPass PostDown;
@@ -1415,8 +1420,6 @@
method public static androidx.ui.painting.Canvas Canvas(androidx.ui.painting.Image image);
method public static androidx.ui.painting.Canvas Canvas(androidx.ui.painting.PictureRecorder recorder, androidx.ui.engine.geometry.Rect cullRect = Rect.largest);
method public static androidx.ui.painting.Canvas Canvas(android.graphics.Canvas c);
- method public static inline void withSave(androidx.ui.painting.Canvas, kotlin.jvm.functions.Function0<kotlin.Unit> block);
- method public static inline void withSaveLayer(androidx.ui.painting.Canvas, androidx.ui.engine.geometry.Rect? bounds, androidx.ui.painting.Paint paint, kotlin.jvm.functions.Function0<kotlin.Unit> block);
}
public final class AndroidImageKt {
@@ -1471,14 +1474,13 @@
method public void clipRect(androidx.ui.engine.geometry.Rect rect, androidx.ui.painting.ClipOp clipOp = ClipOp.intersect);
method public void concat(androidx.ui.vectormath64.Matrix4 matrix4);
method public void drawArc(androidx.ui.engine.geometry.Rect rect, float startAngle, float sweepAngle, boolean useCenter, androidx.ui.painting.Paint paint);
+ method public default void drawArcRad(androidx.ui.engine.geometry.Rect rect, float startAngleRad, float sweepAngleRad, boolean useCenter, androidx.ui.painting.Paint paint);
method public void drawCircle(androidx.ui.engine.geometry.Offset center, float radius, androidx.ui.painting.Paint paint);
- method public void drawColor(androidx.ui.graphics.Color color, androidx.ui.painting.BlendMode blendMode);
method public void drawDRRect(androidx.ui.engine.geometry.RRect outer, androidx.ui.engine.geometry.RRect inner, androidx.ui.painting.Paint paint);
method public void drawImage(androidx.ui.painting.Image image, androidx.ui.engine.geometry.Offset topLeftOffset, androidx.ui.painting.Paint paint);
method public void drawImageRect(androidx.ui.painting.Image image, androidx.ui.engine.geometry.Rect src, androidx.ui.engine.geometry.Rect dst, androidx.ui.painting.Paint paint);
method public void drawLine(androidx.ui.engine.geometry.Offset p1, androidx.ui.engine.geometry.Offset p2, androidx.ui.painting.Paint paint);
method public void drawOval(androidx.ui.engine.geometry.Rect rect, androidx.ui.painting.Paint paint);
- method public void drawPaint(androidx.ui.painting.Paint paint);
method public void drawPath(androidx.ui.painting.Path path, androidx.ui.painting.Paint paint);
method public void drawPicture(androidx.ui.painting.Picture picture);
method public void drawPoints(androidx.ui.painting.PointMode pointMode, java.util.List<androidx.ui.engine.geometry.Offset> points, androidx.ui.painting.Paint paint);
@@ -1489,16 +1491,20 @@
method public android.graphics.Canvas getNativeCanvas();
method public void restore();
method public void rotate(float degrees);
+ method public default void rotateRad(float radians);
method public void save();
- method public void saveLayer(androidx.ui.engine.geometry.Rect? bounds, androidx.ui.painting.Paint paint);
+ method public void saveLayer(androidx.ui.engine.geometry.Rect bounds, androidx.ui.painting.Paint paint);
method public void scale(float sx, float sy = sx);
method public void skew(float sx, float sy);
+ method public default void skewRad(float sxRad, float syRad);
method public void translate(float dx, float dy);
property public abstract android.graphics.Canvas nativeCanvas;
}
public final class CanvasKt {
ctor public CanvasKt();
+ method public static inline void withSave(androidx.ui.painting.Canvas, kotlin.jvm.functions.Function0<kotlin.Unit> block);
+ method public static inline void withSaveLayer(androidx.ui.painting.Canvas, androidx.ui.engine.geometry.Rect bounds, androidx.ui.painting.Paint paint, kotlin.jvm.functions.Function0<kotlin.Unit> block);
}
public enum ClipOp {
@@ -1830,27 +1836,6 @@
method public static String substring(CharSequence, androidx.ui.text.TextRange range);
}
- public final class TextSelection {
- ctor public TextSelection(int baseOffset, int extentOffset, boolean isDirectional);
- method public int component1();
- method public int component2();
- method public boolean component3();
- method public androidx.ui.text.TextSelection copy(int baseOffset, int extentOffset, boolean isDirectional);
- method public int getBaseOffset();
- method public int getEnd();
- method public int getExtentOffset();
- method public int getStart();
- method public boolean isDirectional();
- property public final int end;
- property public final int start;
- field public static final androidx.ui.text.TextSelection.Companion! Companion;
- }
-
- public static final class TextSelection.Companion {
- method public androidx.ui.text.TextSelection collapsed(int offset);
- method public androidx.ui.text.TextSelection fromPosition(int position);
- }
-
}
package androidx.ui.text.style {
diff --git a/ui/ui-core/api/api_lint.ignore b/ui/ui-core/api/api_lint.ignore
index 35dd316..d5d06cc 100644
--- a/ui/ui-core/api/api_lint.ignore
+++ b/ui/ui-core/api/api_lint.ignore
@@ -193,8 +193,6 @@
Missing nullability on field `Companion` in class `class androidx.ui.painting.MaskFilter`
MissingNullability: androidx.ui.painting.Path#Companion:
Missing nullability on field `Companion` in class `class androidx.ui.painting.Path`
-MissingNullability: androidx.ui.text.TextSelection#Companion:
- Missing nullability on field `Companion` in class `class androidx.ui.text.TextSelection`
MissingNullability: androidx.ui.util.Float16#Companion:
Missing nullability on field `Companion` in class `class androidx.ui.util.Float16`
MissingNullability: androidx.ui.vectormath64.Matrix2#Companion:
diff --git a/ui/ui-core/api/current.txt b/ui/ui-core/api/current.txt
index bac3029..87217df 100644
--- a/ui/ui-core/api/current.txt
+++ b/ui/ui-core/api/current.txt
@@ -432,6 +432,11 @@
method public androidx.ui.core.IntPx getWidth();
}
+ public enum LayoutDirection {
+ enum_constant public static final androidx.ui.core.LayoutDirection Ltr;
+ enum_constant public static final androidx.ui.core.LayoutDirection Rtl;
+ }
+
public enum PointerEventPass {
enum_constant public static final androidx.ui.core.PointerEventPass InitialDown;
enum_constant public static final androidx.ui.core.PointerEventPass PostDown;
@@ -1415,8 +1420,6 @@
method public static androidx.ui.painting.Canvas Canvas(androidx.ui.painting.Image image);
method public static androidx.ui.painting.Canvas Canvas(androidx.ui.painting.PictureRecorder recorder, androidx.ui.engine.geometry.Rect cullRect = Rect.largest);
method public static androidx.ui.painting.Canvas Canvas(android.graphics.Canvas c);
- method public static inline void withSave(androidx.ui.painting.Canvas, kotlin.jvm.functions.Function0<kotlin.Unit> block);
- method public static inline void withSaveLayer(androidx.ui.painting.Canvas, androidx.ui.engine.geometry.Rect? bounds, androidx.ui.painting.Paint paint, kotlin.jvm.functions.Function0<kotlin.Unit> block);
}
public final class AndroidImageKt {
@@ -1471,14 +1474,13 @@
method public void clipRect(androidx.ui.engine.geometry.Rect rect, androidx.ui.painting.ClipOp clipOp = ClipOp.intersect);
method public void concat(androidx.ui.vectormath64.Matrix4 matrix4);
method public void drawArc(androidx.ui.engine.geometry.Rect rect, float startAngle, float sweepAngle, boolean useCenter, androidx.ui.painting.Paint paint);
+ method public default void drawArcRad(androidx.ui.engine.geometry.Rect rect, float startAngleRad, float sweepAngleRad, boolean useCenter, androidx.ui.painting.Paint paint);
method public void drawCircle(androidx.ui.engine.geometry.Offset center, float radius, androidx.ui.painting.Paint paint);
- method public void drawColor(androidx.ui.graphics.Color color, androidx.ui.painting.BlendMode blendMode);
method public void drawDRRect(androidx.ui.engine.geometry.RRect outer, androidx.ui.engine.geometry.RRect inner, androidx.ui.painting.Paint paint);
method public void drawImage(androidx.ui.painting.Image image, androidx.ui.engine.geometry.Offset topLeftOffset, androidx.ui.painting.Paint paint);
method public void drawImageRect(androidx.ui.painting.Image image, androidx.ui.engine.geometry.Rect src, androidx.ui.engine.geometry.Rect dst, androidx.ui.painting.Paint paint);
method public void drawLine(androidx.ui.engine.geometry.Offset p1, androidx.ui.engine.geometry.Offset p2, androidx.ui.painting.Paint paint);
method public void drawOval(androidx.ui.engine.geometry.Rect rect, androidx.ui.painting.Paint paint);
- method public void drawPaint(androidx.ui.painting.Paint paint);
method public void drawPath(androidx.ui.painting.Path path, androidx.ui.painting.Paint paint);
method public void drawPicture(androidx.ui.painting.Picture picture);
method public void drawPoints(androidx.ui.painting.PointMode pointMode, java.util.List<androidx.ui.engine.geometry.Offset> points, androidx.ui.painting.Paint paint);
@@ -1489,16 +1491,20 @@
method public android.graphics.Canvas getNativeCanvas();
method public void restore();
method public void rotate(float degrees);
+ method public default void rotateRad(float radians);
method public void save();
- method public void saveLayer(androidx.ui.engine.geometry.Rect? bounds, androidx.ui.painting.Paint paint);
+ method public void saveLayer(androidx.ui.engine.geometry.Rect bounds, androidx.ui.painting.Paint paint);
method public void scale(float sx, float sy = sx);
method public void skew(float sx, float sy);
+ method public default void skewRad(float sxRad, float syRad);
method public void translate(float dx, float dy);
property public abstract android.graphics.Canvas nativeCanvas;
}
public final class CanvasKt {
ctor public CanvasKt();
+ method public static inline void withSave(androidx.ui.painting.Canvas, kotlin.jvm.functions.Function0<kotlin.Unit> block);
+ method public static inline void withSaveLayer(androidx.ui.painting.Canvas, androidx.ui.engine.geometry.Rect bounds, androidx.ui.painting.Paint paint, kotlin.jvm.functions.Function0<kotlin.Unit> block);
}
public enum ClipOp {
@@ -1830,27 +1836,6 @@
method public static String substring(CharSequence, androidx.ui.text.TextRange range);
}
- public final class TextSelection {
- ctor public TextSelection(int baseOffset, int extentOffset, boolean isDirectional);
- method public int component1();
- method public int component2();
- method public boolean component3();
- method public androidx.ui.text.TextSelection copy(int baseOffset, int extentOffset, boolean isDirectional);
- method public int getBaseOffset();
- method public int getEnd();
- method public int getExtentOffset();
- method public int getStart();
- method public boolean isDirectional();
- property public final int end;
- property public final int start;
- field public static final androidx.ui.text.TextSelection.Companion! Companion;
- }
-
- public static final class TextSelection.Companion {
- method public androidx.ui.text.TextSelection collapsed(int offset);
- method public androidx.ui.text.TextSelection fromPosition(int position);
- }
-
}
package androidx.ui.text.style {
diff --git a/ui/ui-core/api/restricted_1.0.0-alpha01.txt b/ui/ui-core/api/restricted_1.0.0-alpha01.txt
index bac3029..87217df 100644
--- a/ui/ui-core/api/restricted_1.0.0-alpha01.txt
+++ b/ui/ui-core/api/restricted_1.0.0-alpha01.txt
@@ -432,6 +432,11 @@
method public androidx.ui.core.IntPx getWidth();
}
+ public enum LayoutDirection {
+ enum_constant public static final androidx.ui.core.LayoutDirection Ltr;
+ enum_constant public static final androidx.ui.core.LayoutDirection Rtl;
+ }
+
public enum PointerEventPass {
enum_constant public static final androidx.ui.core.PointerEventPass InitialDown;
enum_constant public static final androidx.ui.core.PointerEventPass PostDown;
@@ -1415,8 +1420,6 @@
method public static androidx.ui.painting.Canvas Canvas(androidx.ui.painting.Image image);
method public static androidx.ui.painting.Canvas Canvas(androidx.ui.painting.PictureRecorder recorder, androidx.ui.engine.geometry.Rect cullRect = Rect.largest);
method public static androidx.ui.painting.Canvas Canvas(android.graphics.Canvas c);
- method public static inline void withSave(androidx.ui.painting.Canvas, kotlin.jvm.functions.Function0<kotlin.Unit> block);
- method public static inline void withSaveLayer(androidx.ui.painting.Canvas, androidx.ui.engine.geometry.Rect? bounds, androidx.ui.painting.Paint paint, kotlin.jvm.functions.Function0<kotlin.Unit> block);
}
public final class AndroidImageKt {
@@ -1471,14 +1474,13 @@
method public void clipRect(androidx.ui.engine.geometry.Rect rect, androidx.ui.painting.ClipOp clipOp = ClipOp.intersect);
method public void concat(androidx.ui.vectormath64.Matrix4 matrix4);
method public void drawArc(androidx.ui.engine.geometry.Rect rect, float startAngle, float sweepAngle, boolean useCenter, androidx.ui.painting.Paint paint);
+ method public default void drawArcRad(androidx.ui.engine.geometry.Rect rect, float startAngleRad, float sweepAngleRad, boolean useCenter, androidx.ui.painting.Paint paint);
method public void drawCircle(androidx.ui.engine.geometry.Offset center, float radius, androidx.ui.painting.Paint paint);
- method public void drawColor(androidx.ui.graphics.Color color, androidx.ui.painting.BlendMode blendMode);
method public void drawDRRect(androidx.ui.engine.geometry.RRect outer, androidx.ui.engine.geometry.RRect inner, androidx.ui.painting.Paint paint);
method public void drawImage(androidx.ui.painting.Image image, androidx.ui.engine.geometry.Offset topLeftOffset, androidx.ui.painting.Paint paint);
method public void drawImageRect(androidx.ui.painting.Image image, androidx.ui.engine.geometry.Rect src, androidx.ui.engine.geometry.Rect dst, androidx.ui.painting.Paint paint);
method public void drawLine(androidx.ui.engine.geometry.Offset p1, androidx.ui.engine.geometry.Offset p2, androidx.ui.painting.Paint paint);
method public void drawOval(androidx.ui.engine.geometry.Rect rect, androidx.ui.painting.Paint paint);
- method public void drawPaint(androidx.ui.painting.Paint paint);
method public void drawPath(androidx.ui.painting.Path path, androidx.ui.painting.Paint paint);
method public void drawPicture(androidx.ui.painting.Picture picture);
method public void drawPoints(androidx.ui.painting.PointMode pointMode, java.util.List<androidx.ui.engine.geometry.Offset> points, androidx.ui.painting.Paint paint);
@@ -1489,16 +1491,20 @@
method public android.graphics.Canvas getNativeCanvas();
method public void restore();
method public void rotate(float degrees);
+ method public default void rotateRad(float radians);
method public void save();
- method public void saveLayer(androidx.ui.engine.geometry.Rect? bounds, androidx.ui.painting.Paint paint);
+ method public void saveLayer(androidx.ui.engine.geometry.Rect bounds, androidx.ui.painting.Paint paint);
method public void scale(float sx, float sy = sx);
method public void skew(float sx, float sy);
+ method public default void skewRad(float sxRad, float syRad);
method public void translate(float dx, float dy);
property public abstract android.graphics.Canvas nativeCanvas;
}
public final class CanvasKt {
ctor public CanvasKt();
+ method public static inline void withSave(androidx.ui.painting.Canvas, kotlin.jvm.functions.Function0<kotlin.Unit> block);
+ method public static inline void withSaveLayer(androidx.ui.painting.Canvas, androidx.ui.engine.geometry.Rect bounds, androidx.ui.painting.Paint paint, kotlin.jvm.functions.Function0<kotlin.Unit> block);
}
public enum ClipOp {
@@ -1830,27 +1836,6 @@
method public static String substring(CharSequence, androidx.ui.text.TextRange range);
}
- public final class TextSelection {
- ctor public TextSelection(int baseOffset, int extentOffset, boolean isDirectional);
- method public int component1();
- method public int component2();
- method public boolean component3();
- method public androidx.ui.text.TextSelection copy(int baseOffset, int extentOffset, boolean isDirectional);
- method public int getBaseOffset();
- method public int getEnd();
- method public int getExtentOffset();
- method public int getStart();
- method public boolean isDirectional();
- property public final int end;
- property public final int start;
- field public static final androidx.ui.text.TextSelection.Companion! Companion;
- }
-
- public static final class TextSelection.Companion {
- method public androidx.ui.text.TextSelection collapsed(int offset);
- method public androidx.ui.text.TextSelection fromPosition(int position);
- }
-
}
package androidx.ui.text.style {
diff --git a/ui/ui-core/api/restricted_current.txt b/ui/ui-core/api/restricted_current.txt
index bac3029..87217df 100644
--- a/ui/ui-core/api/restricted_current.txt
+++ b/ui/ui-core/api/restricted_current.txt
@@ -432,6 +432,11 @@
method public androidx.ui.core.IntPx getWidth();
}
+ public enum LayoutDirection {
+ enum_constant public static final androidx.ui.core.LayoutDirection Ltr;
+ enum_constant public static final androidx.ui.core.LayoutDirection Rtl;
+ }
+
public enum PointerEventPass {
enum_constant public static final androidx.ui.core.PointerEventPass InitialDown;
enum_constant public static final androidx.ui.core.PointerEventPass PostDown;
@@ -1415,8 +1420,6 @@
method public static androidx.ui.painting.Canvas Canvas(androidx.ui.painting.Image image);
method public static androidx.ui.painting.Canvas Canvas(androidx.ui.painting.PictureRecorder recorder, androidx.ui.engine.geometry.Rect cullRect = Rect.largest);
method public static androidx.ui.painting.Canvas Canvas(android.graphics.Canvas c);
- method public static inline void withSave(androidx.ui.painting.Canvas, kotlin.jvm.functions.Function0<kotlin.Unit> block);
- method public static inline void withSaveLayer(androidx.ui.painting.Canvas, androidx.ui.engine.geometry.Rect? bounds, androidx.ui.painting.Paint paint, kotlin.jvm.functions.Function0<kotlin.Unit> block);
}
public final class AndroidImageKt {
@@ -1471,14 +1474,13 @@
method public void clipRect(androidx.ui.engine.geometry.Rect rect, androidx.ui.painting.ClipOp clipOp = ClipOp.intersect);
method public void concat(androidx.ui.vectormath64.Matrix4 matrix4);
method public void drawArc(androidx.ui.engine.geometry.Rect rect, float startAngle, float sweepAngle, boolean useCenter, androidx.ui.painting.Paint paint);
+ method public default void drawArcRad(androidx.ui.engine.geometry.Rect rect, float startAngleRad, float sweepAngleRad, boolean useCenter, androidx.ui.painting.Paint paint);
method public void drawCircle(androidx.ui.engine.geometry.Offset center, float radius, androidx.ui.painting.Paint paint);
- method public void drawColor(androidx.ui.graphics.Color color, androidx.ui.painting.BlendMode blendMode);
method public void drawDRRect(androidx.ui.engine.geometry.RRect outer, androidx.ui.engine.geometry.RRect inner, androidx.ui.painting.Paint paint);
method public void drawImage(androidx.ui.painting.Image image, androidx.ui.engine.geometry.Offset topLeftOffset, androidx.ui.painting.Paint paint);
method public void drawImageRect(androidx.ui.painting.Image image, androidx.ui.engine.geometry.Rect src, androidx.ui.engine.geometry.Rect dst, androidx.ui.painting.Paint paint);
method public void drawLine(androidx.ui.engine.geometry.Offset p1, androidx.ui.engine.geometry.Offset p2, androidx.ui.painting.Paint paint);
method public void drawOval(androidx.ui.engine.geometry.Rect rect, androidx.ui.painting.Paint paint);
- method public void drawPaint(androidx.ui.painting.Paint paint);
method public void drawPath(androidx.ui.painting.Path path, androidx.ui.painting.Paint paint);
method public void drawPicture(androidx.ui.painting.Picture picture);
method public void drawPoints(androidx.ui.painting.PointMode pointMode, java.util.List<androidx.ui.engine.geometry.Offset> points, androidx.ui.painting.Paint paint);
@@ -1489,16 +1491,20 @@
method public android.graphics.Canvas getNativeCanvas();
method public void restore();
method public void rotate(float degrees);
+ method public default void rotateRad(float radians);
method public void save();
- method public void saveLayer(androidx.ui.engine.geometry.Rect? bounds, androidx.ui.painting.Paint paint);
+ method public void saveLayer(androidx.ui.engine.geometry.Rect bounds, androidx.ui.painting.Paint paint);
method public void scale(float sx, float sy = sx);
method public void skew(float sx, float sy);
+ method public default void skewRad(float sxRad, float syRad);
method public void translate(float dx, float dy);
property public abstract android.graphics.Canvas nativeCanvas;
}
public final class CanvasKt {
ctor public CanvasKt();
+ method public static inline void withSave(androidx.ui.painting.Canvas, kotlin.jvm.functions.Function0<kotlin.Unit> block);
+ method public static inline void withSaveLayer(androidx.ui.painting.Canvas, androidx.ui.engine.geometry.Rect bounds, androidx.ui.painting.Paint paint, kotlin.jvm.functions.Function0<kotlin.Unit> block);
}
public enum ClipOp {
@@ -1830,27 +1836,6 @@
method public static String substring(CharSequence, androidx.ui.text.TextRange range);
}
- public final class TextSelection {
- ctor public TextSelection(int baseOffset, int extentOffset, boolean isDirectional);
- method public int component1();
- method public int component2();
- method public boolean component3();
- method public androidx.ui.text.TextSelection copy(int baseOffset, int extentOffset, boolean isDirectional);
- method public int getBaseOffset();
- method public int getEnd();
- method public int getExtentOffset();
- method public int getStart();
- method public boolean isDirectional();
- property public final int end;
- property public final int start;
- field public static final androidx.ui.text.TextSelection.Companion! Companion;
- }
-
- public static final class TextSelection.Companion {
- method public androidx.ui.text.TextSelection collapsed(int offset);
- method public androidx.ui.text.TextSelection fromPosition(int position);
- }
-
}
package androidx.ui.text.style {
diff --git a/ui/ui-core/src/main/java/androidx/ui/core/LayoutDirection.kt b/ui/ui-core/src/main/java/androidx/ui/core/LayoutDirection.kt
new file mode 100644
index 0000000..c826d32
--- /dev/null
+++ b/ui/ui-core/src/main/java/androidx/ui/core/LayoutDirection.kt
@@ -0,0 +1,34 @@
+/*
+ * 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
+
+/**
+ * A class for defining layout directions.
+ *
+ * A layout direction can be left-to-right (LTR) or right-to-left (RTL).
+ */
+enum class LayoutDirection {
+ /**
+ * Horizontal layout direction is from Left to Right.
+ */
+ Ltr,
+
+ /**
+ * Horizontal layout direction is from Right to Left.
+ */
+ Rtl,
+}
\ No newline at end of file
diff --git a/ui/ui-core/src/main/java/androidx/ui/painting/AndroidCanvas.kt b/ui/ui-core/src/main/java/androidx/ui/painting/AndroidCanvas.kt
index 80eecbb..684d81c 100644
--- a/ui/ui-core/src/main/java/androidx/ui/painting/AndroidCanvas.kt
+++ b/ui/ui-core/src/main/java/androidx/ui/painting/AndroidCanvas.kt
@@ -19,11 +19,9 @@
import android.graphics.Matrix
import androidx.ui.Vertices
import androidx.ui.core.toFrameworkRect
-import androidx.ui.core.toFrameworkRectF
import androidx.ui.engine.geometry.Offset
import androidx.ui.engine.geometry.RRect
import androidx.ui.engine.geometry.Rect
-import androidx.ui.graphics.Color
import androidx.ui.vectormath64.Matrix4
import androidx.ui.vectormath64.isIdentity
@@ -52,96 +50,6 @@
fun Canvas(c: android.graphics.Canvas): Canvas = AndroidCanvas(c)
-/**
- * Saves a copy of the current transform and clip on the save stack and executes the
- * provided lambda with the current transform applied. Once the lambda has been executed,
- * the transformation is popped from the stack, undoing the transformation.
- *
- *
- * See also:
- *
- * * [saveLayer], which does the same thing but additionally also groups the
- * commands
- */
-/* expect */ inline fun Canvas.withSave(block: () -> Unit) {
- try {
- nativeCanvas.save()
- block()
- } finally {
- nativeCanvas.restore()
- }
-}
-
-/**
- * Saves a copy of the current transform and clip on the save stack, and then
- * creates a new group which subsequent calls will become a part of. When the
- * lambda is executed and the save stack is popped, the group will be flattened into
- * a layer and have the given `paint`'s [Paint.colorFilter] and [Paint.blendMode]
- * applied.
- *
- * This lets you create composite effects, for example making a group of
- * drawing commands semi-transparent. Without using [saveLayer], each part of
- * the group would be painted individually, so where they overlap would be
- * darker than where they do not. By using [saveLayer] to group them
- * together, they can be drawn with an opaque color at first, and then the
- * entire group can be made transparent using the [saveLayer]'s paint.
- *
- *
- * ## Using saveLayer with clips
- *
- * When a rectangular clip operation (from [clipRect]) is not axis-aligned
- * with the raster buffer, or when the clip operation is not rectalinear (e.g.
- * because it is a rounded rectangle clip created by [clipRRect] or an
- * arbitrarily complicated path clip created by [clipPath]), the edge of the
- * clip needs to be anti-aliased.
- *
- * If two draw calls overlap at the edge of such a clipped region, without
- * using [saveLayer], the first drawing will be anti-aliased with the
- * background first, and then the second will be anti-aliased with the result
- * of blending the first drawing and the background. On the other hand, if
- * [saveLayer] is used immediately after establishing the clip, the second
- * drawing will cover the first in the layer, and thus the second alone will
- * be anti-aliased with the background when the layer is clipped and
- * composited (when lambda is finished executing).
- *
- * (Incidentally, rather than using [clipRRect] and [drawPaint] to draw
- * rounded rectangles like this, prefer the [drawRRect] method. These
- * examples are using [drawPaint] as a proxy for "complicated draw operations
- * that will get clipped", to illustrate the point.)
- *
- * ## Performance considerations
- *
- * Generally speaking, [saveLayer] is relatively expensive.
- *
- * There are a several different hardware architectures for GPUs (graphics
- * processing units, the hardware that handles graphics), but most of them
- * involve batching commands and reordering them for performance. When layers
- * are used, they cause the rendering pipeline to have to switch render
- * target (from one layer to another). Render target switches can flush the
- * GPU's command buffer, which typically means that optimizations that one
- * could get with larger batching are lost. Render target switches also
- * generate a lot of memory churn because the GPU needs to copy out the
- * current frame buffer contents from the part of memory that's optimized for
- * writing, and then needs to copy it back in once the previous render target
- * (layer) is restored.
- *
- * See also:
- *
- * * [save], which saves the current state, but does not create a new layer
- * for subsequent commands.
- * * [BlendMode], which discusses the use of [Paint.blendMode] with
- * [saveLayer].
- */
-@SuppressWarnings("deprecation")
-inline fun Canvas.withSaveLayer(bounds: Rect?, paint: Paint, block: () -> Unit) {
- try {
- nativeCanvas.saveLayer(bounds?.toFrameworkRectF(), paint.asFrameworkPaint())
- block()
- } finally {
- nativeCanvas.restore()
- }
-}
-
private class AndroidCanvas(val internalCanvas: android.graphics.Canvas) : Canvas {
private val internalPath = Path()
@@ -169,33 +77,18 @@
* @see Canvas.saveLayer
*/
@SuppressWarnings("deprecation")
- override fun saveLayer(bounds: Rect?, paint: Paint) {
- if (bounds == null) {
- TODO("Migration/njawad framework does not have " +
- "equivalent for saveLayerWithoutBounds")
- } else {
- @Suppress("DEPRECATION")
- internalCanvas.saveLayer(
- bounds.left,
- bounds.top,
- bounds.right,
- bounds.bottom,
- paint.asFrameworkPaint(),
- android.graphics.Canvas.ALL_SAVE_FLAG
- )
- }
+ override fun saveLayer(bounds: Rect, paint: Paint) {
+ @Suppress("DEPRECATION")
+ internalCanvas.saveLayer(
+ bounds.left,
+ bounds.top,
+ bounds.right,
+ bounds.bottom,
+ paint.asFrameworkPaint(),
+ android.graphics.Canvas.ALL_SAVE_FLAG
+ )
}
- // TODO(Migration/njawad find equivalent implementation for _saveLayerWithoutBounds or not
-// void _saveLayerWithoutBounds(List<dynamic> paintObjects, ByteData paintData)
-// native 'Canvas_saveLayerWithoutBounds';
-// void _saveLayer(double left,
-// double top,
-// double right,
-// double bottom,
-// List<dynamic> paintObjects,
-// ByteData paintData) native 'Canvas_saveLayer';
-
/**
* @see Canvas.translate
*/
@@ -286,13 +179,6 @@
}
/**
- * @see Canvas.drawColor
- */
- override fun drawColor(color: Color, blendMode: BlendMode) {
- internalCanvas.drawColor(color.toArgb(), blendMode.toPorterDuffMode())
- }
-
- /**
* @see Canvas.drawLine
*/
override fun drawLine(p1: Offset, p2: Offset, paint: Paint) {
@@ -306,13 +192,6 @@
}
/**
- * @see Canvas.drawPaint
- */
- override fun drawPaint(paint: Paint) {
- internalCanvas.drawPaint(paint.asFrameworkPaint())
- }
-
- /**
* @see Canvas.drawRect
*/
override fun drawRect(rect: Rect, paint: Paint) {
@@ -552,10 +431,4 @@
paint.asFrameworkPaint()
)
}
-
- // TODO(Migration/njawad add drawAtlas API)
-
- // TODO(Migration/njawad add drawRawAtlas API)
-
- // TODO(Migration/njawad add drawShadow API)
}
\ No newline at end of file
diff --git a/ui/ui-core/src/main/java/androidx/ui/painting/Canvas.kt b/ui/ui-core/src/main/java/androidx/ui/painting/Canvas.kt
index a632833..ac02b65 100644
--- a/ui/ui-core/src/main/java/androidx/ui/painting/Canvas.kt
+++ b/ui/ui-core/src/main/java/androidx/ui/painting/Canvas.kt
@@ -20,32 +20,25 @@
import androidx.ui.engine.geometry.Offset
import androidx.ui.engine.geometry.RRect
import androidx.ui.engine.geometry.Rect
-import androidx.ui.graphics.Color
import androidx.ui.vectormath64.Matrix4
+import androidx.ui.vectormath64.PI
// TODO(mount/njawad): Separate the platform-independent API from the platform-dependent.
// TODO(Migration/njawad): Copy the class here
/**
* An interface for recording graphical operations.
*
- * [Canvas] objects are used in creating [Picture] objects, which can
- * themselves be used with a [SceneBuilder] to build a [Scene]. In
- * normal usage, however, this is all handled by the framework.
- *
* A canvas has a current transformation matrix which is applied to all
* operations. Initially, the transformation matrix is the identity transform.
- * It can be modified using the [translate], [scale], [rotate], [skew],
- * and [transform] methods.
+ * It can be modified using the [Canvas.translate], [Canvas.scale], [Canvas.rotate], [Canvas.skew] methods.
*
* A canvas also has a current clip region which is applied to all operations.
* Initially, the clip region is infinite. It can be modified using the
- * [clipRect], [clipRRect], and [clipPath] methods.
+ * [Canvas.clipRect], [Canvas.clipRRect], and [Canvas.clipPath] methods.
*
* The current transform and clip can be saved and restored using the stack
- * managed by the [save], [saveLayer], and [restore] methods.
- */
+ * managed by the [Canvas.save], [Canvas.saveLayer], and [Canvas.restore] methods.
-/**
* Creates a canvas for recording graphical operations into the
* given picture recorder.
*
@@ -53,7 +46,7 @@
* `cullRect` might be discarded by the implementation. However, the
* implementation might draw outside these bounds if, for example, a command
* draws partially inside and outside the `cullRect`. To ensure that pixels
- * outside a given region are discarded, consider using a [clipRect]. The
+ * outside a given region are discarded, consider using a [Canvas.clipRect]. The
* `cullRect` is optional; by default, all operations are kept.
*
* To end the recording, call [PictureRecorder.endRecording] on the
@@ -69,6 +62,91 @@
/* expect */ typealias NativeCanvas = android.graphics.Canvas
+/**
+ * Saves a copy of the current transform and clip on the save stack and executes the
+ * provided lambda with the current transform applied. Once the lambda has been executed,
+ * the transformation is popped from the stack, undoing the transformation.
+ *
+ *
+ * See also:
+ *
+ * [Canvas.saveLayer], which does the same thing but additionally also groups the
+ * commands
+ */
+/* expect */ inline fun Canvas.withSave(block: () -> Unit) {
+ try {
+ save()
+ block()
+ } finally {
+ restore()
+ }
+}
+
+/**
+ * Saves a copy of the current transform and clip on the save stack, and then
+ * creates a new group which subsequent calls will become a part of. When the
+ * lambda is executed and the save stack is popped, the group will be flattened into
+ * a layer and have the given `paint`'s [Paint.colorFilter] and [Paint.blendMode]
+ * applied.
+ *
+ * This lets you create composite effects, for example making a group of
+ * drawing commands semi-transparent. Without using [Canvas.saveLayer], each part of
+ * the group would be painted individually, so where they overlap would be
+ * darker than where they do not. By using [Canvas.saveLayer] to group them
+ * together, they can be drawn with an opaque color at first, and then the
+ * entire group can be made transparent using the [Canvas.saveLayer]'s paint.
+ *
+ *
+ * ## Using saveLayer with clips
+ *
+ * When a rectangular clip operation (from [Canvas.clipRect]) is not axis-aligned
+ * with the raster buffer, or when the clip operation is not rectalinear (e.g.
+ * because it is a rounded rectangle clip created by [Canvas.clipRRect] or an
+ * arbitrarily complicated path clip created by [Canvas.clipPath]), the edge of the
+ * clip needs to be anti-aliased.
+ *
+ * If two draw calls overlap at the edge of such a clipped region, without
+ * using [Canvas.saveLayer], the first drawing will be anti-aliased with the
+ * background first, and then the second will be anti-aliased with the result
+ * of blending the first drawing and the background. On the other hand, if
+ * [Canvas.saveLayer] is used immediately after establishing the clip, the second
+ * drawing will cover the first in the layer, and thus the second alone will
+ * be anti-aliased with the background when the layer is clipped and
+ * composited (when lambda is finished executing).
+ *
+ * ## Performance considerations
+ *
+ * Generally speaking, [Canvas.saveLayer] is relatively expensive.
+ *
+ * There are a several different hardware architectures for GPUs (graphics
+ * processing units, the hardware that handles graphics), but most of them
+ * involve batching commands and reordering them for performance. When layers
+ * are used, they cause the rendering pipeline to have to switch render
+ * target (from one layer to another). Render target switches can flush the
+ * GPU's command buffer, which typically means that optimizations that one
+ * could get with larger batching are lost. Render target switches also
+ * generate a lot of memory churn because the GPU needs to copy out the
+ * current frame buffer contents from the part of memory that's optimized for
+ * writing, and then needs to copy it back in once the previous render target
+ * (layer) is restored.
+ *
+ * See also:
+ *
+ * * [Canvas.save], which saves the current state, but does not create a new layer
+ * for subsequent commands.
+ * * [BlendMode], which discusses the use of [Paint.blendMode] with
+ * [saveLayer].
+ */
+@SuppressWarnings("deprecation")
+inline fun Canvas.withSaveLayer(bounds: Rect, paint: Paint, block: () -> Unit) {
+ try {
+ saveLayer(bounds, paint)
+ block()
+ } finally {
+ restore()
+ }
+}
+
interface Canvas {
/**
@@ -86,7 +164,6 @@
* * [saveLayer], which does the same thing but additionally also groups the
* commands done until the matching [restore].
*/
- // TODO (njawad) replace with lambda overload when multi-child ComponentNode support is added
fun save()
/**
@@ -98,8 +175,6 @@
* If the state was pushed with with [saveLayer], then this call will also
* cause the new layer to be composited into the previous layer.
*/
- // TODO (njawad) remove when save with lambda receiver overload is supported with multi-child
- // ComponentNodes
fun restore()
/**
@@ -135,60 +210,10 @@
* be anti-aliased with the background when the layer is clipped and
* composited (when [restore] is called).
*
- * For example, this [CustomPainter.paint] method paints a clean white
- * rounded rectangle:
+
*
- * ```dart
- * void paint(Canvas canvas, Size size) {
- * Rect rect = Offset.zero & size;
- * canvas.save();
- * canvas.clipRRect(new RRect.fromRectXY(rect, 100.0, 100.0));
- * canvas.saveLayer(rect, new Paint());
- * canvas.drawPaint(new Paint()..color = Colors.red);
- * canvas.drawPaint(new Paint()..color = Colors.white);
- * canvas.restore();
- * canvas.restore();
- * }
- * ```
- *
- * On the other hand, this one renders a red outline, the result of the red
- * paint being anti-aliased with the background at the clip edge, then the
- * white paint being similarly anti-aliased with the background _including
- * the clipped red paint_:
- *
- * ```dart
- * void paint(Canvas canvas, Size size) {
- * // (this example renders poorly, prefer the example above)
- * Rect rect = Offset.zero & size;
- * canvas.save();
- * canvas.clipRRect(new RRect.fromRectXY(rect, 100.0, 100.0));
- * canvas.drawPaint(new Paint()..color = Colors.red);
- * canvas.drawPaint(new Paint()..color = Colors.white);
- * canvas.restore();
- * }
- * ```
- *
- * This point is moot if the clip only clips one draw operation. For example,
- * the following paint method paints a pair of clean white rounded
- * rectangles, even though the clips are not done on a separate layer:
- *
- * ```dart
- * void paint(Canvas canvas, Size size) {
- * canvas.save();
- * canvas.clipRRect(new RRect.fromRectXY(Offset.zero & (size / 2.0), 50.0, 50.0));
- * canvas.drawPaint(new Paint()..color = Colors.white);
- * canvas.restore();
- * canvas.save();
- * canvas.clipRRect(new RRect.fromRectXY(size.center(Offset.zero) & (size / 2.0), 50.0, 50.0));
- * canvas.drawPaint(new Paint()..color = Colors.white);
- * canvas.restore();
- * }
- * ```
- *
- * (Incidentally, rather than using [clipRRect] and [drawPaint] to draw
- * rounded rectangles like this, prefer the [drawRRect] method. These
- * examples are using [drawPaint] as a proxy for "complicated draw operations
- * that will get clipped", to illustrate the point.)
+ * (Incidentally, rather than using [clipRRect] to draw
+ * rounded rectangles like this, prefer the [drawRRect] method.
*
* ## Performance considerations
*
@@ -213,18 +238,7 @@
* * [BlendMode], which discusses the use of [Paint.blendMode] with
* [saveLayer].
*/
- @SuppressWarnings("deprecation")
- fun saveLayer(bounds: Rect?, paint: Paint)
-
- // TODO(Migration/njawad find equivalent implementation for _saveLayerWithoutBounds or not
-// void _saveLayerWithoutBounds(List<dynamic> paintObjects, ByteData paintData)
-// native 'Canvas_saveLayerWithoutBounds';
-// void _saveLayer(double left,
-// double top,
-// double right,
-// double bottom,
-// List<dynamic> paintObjects,
-// ByteData paintData) native 'Canvas_saveLayer';
+ fun saveLayer(bounds: Rect, paint: Paint)
/**
* Add a translation to the current transform, shifting the coordinate space
@@ -245,6 +259,11 @@
/** Add a rotation to the current transform. The argument is in degrees clockwise. */
fun rotate(degrees: Float)
+ /** Add a rotation to the current transform. The argument is in radians clockwise. */
+ fun rotateRad(radians: Float) {
+ rotate(radians.toDegrees())
+ }
+
/**
* Add an axis-aligned skew to the current transform, with the first argument
* being the horizontal skew in degrees clockwise around the origin, and the
@@ -254,6 +273,16 @@
fun skew(sx: Float, sy: Float)
/**
+ * Add an axis-aligned skew to the current transform, with the first argument
+ * being the horizontal skew in radians clockwise around the origin, and the
+ * second argument being the vertical skew in radians clockwise around the
+ * origin.
+ */
+ fun skewRad(sxRad: Float, syRad: Float) {
+ skew(sxRad.toDegrees(), syRad.toDegrees())
+ }
+
+ /**
* Multiply the current transform by the specified 4⨉4 transformation matrix
* specified as a list of values in column-major order.
*/
@@ -298,13 +327,6 @@
fun clipPath(path: Path)
/**
- * Paints the given [Color] onto the canvas, applying the given
- * [BlendMode], with the given color being the source and the background
- * being the destination.
- */
- fun drawColor(color: Color, blendMode: BlendMode)
-
- /**
* Draws a line between the given points using the given paint. The line is
* stroked, the value of the [Paint.style] is ignored for this call.
*
@@ -313,14 +335,6 @@
fun drawLine(p1: Offset, p2: Offset, paint: Paint)
/**
- * Fills the canvas with the given [Paint].
- *
- * To fill the canvas with a solid color and blend mode, consider
- * [drawColor] instead.
- */
- fun drawPaint(paint: Paint)
-
- /**
* Draws a rectangle with the given [Paint]. Whether the rectangle is filled
* or stroked (or both) is controlled by [Paint.style].
*/
@@ -377,6 +391,28 @@
)
/**
+ * Draw an arc scaled to fit inside the given rectangle. It starts from
+ * startAngle radians around the oval up to startAngle + sweepAngle
+ * radians around the oval, with zero radians being the point on
+ * the right hand side of the oval that crosses the horizontal line
+ * that intersects the center of the rectangle and with positive
+ * angles going clockwise around the oval. If useCenter is true, the arc is
+ * closed back to the center, forming a circle sector. Otherwise, the arc is
+ * not closed, forming a circle segment.
+ *
+ * This method is optimized for drawing arcs and should be faster than [Path.arcTo].
+ */
+ fun drawArcRad(
+ rect: Rect,
+ startAngleRad: Float,
+ sweepAngleRad: Float,
+ useCenter: Boolean,
+ paint: Paint
+ ) {
+ drawArc(rect, startAngleRad.toDegrees(), sweepAngleRad.toDegrees(), useCenter, paint)
+ }
+
+ /**
* Draws the given [Path] with the given [Paint]. Whether this shape is
* filled or stroked (or both) is controlled by [Paint.style]. If the path is
* filled, then subpaths within it are implicitly closed (see [Path.close]).
@@ -389,22 +425,12 @@
*/
fun drawImage(image: Image, topLeftOffset: Offset, paint: Paint)
-// void _drawImage(Image image,
-// double x,
-// double y,
-// List<dynamic> paintObjects,
-// ByteData paintData) native 'Canvas_drawImage';
-//
/**
* Draws the subset of the given image described by the `src` argument into
* the canvas in the axis-aligned rectangle given by the `dst` argument.
*
* This might sample from outside the `src` rect by up to half the width of
* an applied filter.
- *
- * Multiple calls to this method with different arguments (from the same
- * image) can be batched into a single call to [drawAtlas] to improve
- * performance.
*/
fun drawImageRect(image: Image, src: Rect, dst: Rect, paint: Paint)
@@ -415,40 +441,13 @@
fun drawPicture(picture: Picture)
/**
- * Draws the text in the given [Paragraph] into this canvas at the given [Offset].
- *
- * The [Paragraph] object must have had [Paragraph.layout] called on it first.
- *
- * To align the text, set the `textAlign` on the [ParagraphStyle] object passed to the
- * [new ParagraphBuilder] constructor. For more details see [TextAlign] and the discussion at
- * [new ParagraphStyle].
- *
- * If the text is left aligned or justified, the left margin will be at the position specified
- * by the `offset` argument's [Offset.dx] coordinate.
- *
- * If the text is right aligned or justified, the right margin will be at the position described
- * by adding the [ParagraphConstraints.width] given to [Paragraph.layout], to the `offset`
- * argument's [Offset.dx] coordinate.
- *
- * If the text is centered, the centering axis will be at the position described by adding half
- * of the [ParagraphConstraints.width] given to [Paragraph.layout], to the `offset` argument's
- * [Offset.dx] coordinate.
- */
- // TODO(siyamed): Decide what to do with this method. Should it exist on Canvas?
-// fun drawParagraph(paragraph: Paragraph, offset: Offset) {
-// assert(paragraph != null)
-// assert(Offset.isValid(offset))
-// paragraph.paint(this, offset.dx, offset.dy)
-// }
-
- /**
* Draws a sequence of points according to the given [PointMode].
*
* The `points` argument is interpreted as offsets from the origin.
*
* See also:
*
- * * [drawRawPoints], which takes `points` as a [Float32List] rather than a
+ * * [drawRawPoints], which takes `points` as a [FloatArray] rather than a
* [List<Offset>].
*/
fun drawPoints(pointMode: PointMode, points: List<Offset>, paint: Paint)
@@ -465,137 +464,8 @@
* [List<Float32List>].
*/
fun drawRawPoints(pointMode: PointMode, points: FloatArray, paint: Paint)
-//
-// void _drawPoints(List<dynamic> paintObjects,
-// ByteData paintData,
-// int pointMode,
-// Float32List points) native 'Canvas_drawPoints';
- fun drawVertices(vertices: Vertices, blendMode: BlendMode, paint: Paint)
-// //
-// // See also:
-// //
-// // * [drawRawAtlas], which takes its arguments as typed data lists rather
-// // than objects.
- // TODO(Migration/njawad provide canvas atlas support with framework APIs)
-// void drawAtlas(Image atlas,
-// List<RSTransform> transforms,
-// List<Rect> rects,
-// List<Color> colors,
-// BlendMode blendMode,
-// Rect cullRect,
-// Paint paint) {
-// assert(atlas != null); // atlas is checked on the engine side
-// assert(transforms != null);
-// assert(rects != null);
-// assert(colors != null);
-// assert(blendMode != null);
-// assert(paint != null);
-//
-// final int rectCount = rects.length;
-// if (transforms.length != rectCount)
-// throw new ArgumentError('"transforms" and "rects" lengths must match.');
-// if (colors.isNotEmpty && colors.length != rectCount)
-// throw new ArgumentError('If non-null, "colors" length must match that of "transforms" and "rects".');
-//
-// final Float32List rstTransformBuffer = new Float32List(rectCount * 4);
-// final Float32List rectBuffer = new Float32List(rectCount * 4);
-//
-// for (int i = 0; i < rectCount; ++i) {
-// final int index0 = i * 4;
-// final int index1 = index0 + 1;
-// final int index2 = index0 + 2;
-// final int index3 = index0 + 3;
-// final RSTransform rstTransform = transforms[i];
-// final Rect rect = rects[i];
-// assert(_rectIsValid(rect));
-// rstTransformBuffer[index0] = rstTransform.scos;
-// rstTransformBuffer[index1] = rstTransform.ssin;
-// rstTransformBuffer[index2] = rstTransform.tx;
-// rstTransformBuffer[index3] = rstTransform.ty;
-// rectBuffer[index0] = rect.left;
-// rectBuffer[index1] = rect.top;
-// rectBuffer[index2] = rect.right;
-// rectBuffer[index3] = rect.bottom;
-// }
-//
-// final Int32List colorBuffer = colors.isEmpty ? null : _encodeColorList(colors);
-// final Float32List cullRectBuffer = cullRect?._value;
-//
-// _drawAtlas(
-// paint._objects, paint._data, atlas, rstTransformBuffer, rectBuffer,
-// colorBuffer, blendMode.index, cullRectBuffer
-// );
-// }
-//
-// //
-// // The `rstTransforms` argument is interpreted as a list of four-tuples, with
-// // each tuple being ([RSTransform.scos], [RSTransform.ssin],
-// // [RSTransform.tx], [RSTransform.ty]).
-// //
-// // The `rects` argument is interpreted as a list of four-tuples, with each
-// // tuple being ([Rect.left], [Rect.top], [Rect.right], [Rect.bottom]).
-// //
-// // The `colors` argument, which can be null, is interpreted as a list of
-// // 32-bit colors, with the same packing as [Color.value].
-// //
-// // See also:
-// //
-// // * [drawAtlas], which takes its arguments as objects rather than typed
-// // data lists.
- // TODO(Migration/njawad provide canvas atlas support with framework APIs)
-// void drawRawAtlas(Image atlas,
-// Float32List rstTransforms,
-// Float32List rects,
-// Int32List colors,
-// BlendMode blendMode,
-// Rect cullRect,
-// Paint paint) {
-// assert(atlas != null); // atlas is checked on the engine side
-// assert(rstTransforms != null);
-// assert(rects != null);
-// assert(colors != null);
-// assert(blendMode != null);
-// assert(paint != null);
-//
-// final int rectCount = rects.length;
-// if (rstTransforms.length != rectCount)
-// throw new ArgumentError('"rstTransforms" and "rects" lengths must match.');
-// if (rectCount % 4 != 0)
-// throw new ArgumentError('"rstTransforms" and "rects" lengths must be a multiple of four.');
-// if (colors != null && colors.length * 4 != rectCount)
-// throw new ArgumentError('If non-null, "colors" length must be one fourth the length of "rstTransforms" and "rects".');
-//
-// _drawAtlas(
-// paint._objects, paint._data, atlas, rstTransforms, rects,
-// colors, blendMode.index, cullRect?._value
-// );
-// }
-//
-// void _drawAtlas(List<dynamic> paintObjects,
-// ByteData paintData,
-// Image atlas,
-// Float32List rstTransforms,
-// Float32List rects,
-// Int32List colors,
-// int blendMode,
-// Float32List cullRect) native 'Canvas_drawAtlas';
-//
-// /// Draws a shadow for a [Path] representing the given material elevation.
-// ///
-// /// The `transparentOccluder` argument should be true if the occluding object
-// /// is not opaque.
-// ///
-// /// The arguments must not be null.
- // TODO(Migration/njawad provide canvas shadow support with framework APIs)
-// void drawShadow(Path path, Color color, double elevation, bool transparentOccluder) {
-// assert(path != null); // path is checked on the engine side
-// assert(color != null);
-// assert(transparentOccluder != null);
-// _drawShadow(path, color.value, elevation, transparentOccluder);
-// }
-// void _drawShadow(Path path,
-// int color,
-// double elevation,
-// bool transparentOccluder) native 'Canvas_drawShadow';
+ fun drawVertices(vertices: Vertices, blendMode: BlendMode, paint: Paint)
}
+
+private fun Float.toDegrees(): Float = this * 180.0f / PI
\ No newline at end of file
diff --git a/ui/ui-core/src/main/java/androidx/ui/text/TextSelection.kt b/ui/ui-core/src/main/java/androidx/ui/text/TextSelection.kt
deleted file mode 100644
index cb41a4e..0000000
--- a/ui/ui-core/src/main/java/androidx/ui/text/TextSelection.kt
+++ /dev/null
@@ -1,89 +0,0 @@
-/*
- * 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.text
-
-/** A range of text that represents a selection. */
-data class TextSelection(
- /**
- * The offset at which the selection originates.
- *
- * Might be larger than, smaller than, or equal to extent.
- */
- val baseOffset: Int,
-
- /**
- * The offset at which the selection terminates.
- *
- * When the user uses the arrow keys to adjust the selection, this is the
- * value that changes. Similarly, if the current theme paints a caret on one
- * side of the selection, this is the location at which to paint the caret.
- *
- * Might be larger than, smaller than, or equal to base.
- */
- val extentOffset: Int,
-
- /**
- * Whether this selection has disambiguated its base and extent.
- *
- * On some platforms, the base and extent are not disambiguated until the
- * first time the user adjusts the selection. At that point, either the start
- * or the end of the selection becomes the base and the other one becomes the
- * extent and is adjusted.
- */
- val isDirectional: Boolean = false
-) {
- private val range: TextRange = TextRange(
- start = if (baseOffset < extentOffset) baseOffset else extentOffset,
- end = if (baseOffset < extentOffset) extentOffset else baseOffset
- )
-
- val start: Int
- get() = range.start
-
- val end: Int
- get() = range.end
-
- companion object {
- /**
- * Creates a collapsed selection at the given offset.
- *
- * A collapsed selection starts and ends at the same offset, which means it
- * contains zero characters but instead serves as an insertion point in the
- * text.
- */
- fun collapsed(
- offset: Int
- ): TextSelection {
- return TextSelection(
- baseOffset = offset,
- extentOffset = offset,
- isDirectional = false
- )
- }
-
- /**
- * Creates a collapsed selection at the given text position.
- *
- * A collapsed selection starts and ends at the same offset, which means it
- * contains zero characters but instead serves as an insertion point in the
- * text.
- */
- fun fromPosition(position: Int): TextSelection {
- return collapsed(offset = position)
- }
- }
-}
diff --git a/ui/ui-core/src/test/java/androidx/ui/text/TextSelectionTest.kt b/ui/ui-core/src/test/java/androidx/ui/text/TextSelectionTest.kt
deleted file mode 100644
index 26c5837..0000000
--- a/ui/ui-core/src/test/java/androidx/ui/text/TextSelectionTest.kt
+++ /dev/null
@@ -1,46 +0,0 @@
-/*
- * 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.text
-
-import org.hamcrest.CoreMatchers.equalTo
-import org.junit.Assert.assertThat
-import org.junit.Test
-import org.junit.runner.RunWith
-import org.junit.runners.JUnit4
-
-@RunWith(JUnit4::class)
-class TextSelectionTest {
- @Test
- fun collapsed() {
- val offset = 10
- val textSelection = TextSelection.collapsed(offset)
-
- assertThat(offset, equalTo(textSelection.baseOffset))
- assertThat(offset, equalTo(textSelection.extentOffset))
- assertThat(false, equalTo(textSelection.isDirectional))
- }
-
- @Test
- fun fromPosition() {
- val offset = 20
- val textSelection = TextSelection.fromPosition(offset)
-
- assertThat(offset, equalTo(textSelection.baseOffset))
- assertThat(offset, equalTo(textSelection.extentOffset))
- assertThat(false, equalTo(textSelection.isDirectional))
- }
-}
\ No newline at end of file
diff --git a/ui/ui-foundation/api/1.0.0-alpha01.txt b/ui/ui-foundation/api/1.0.0-alpha01.txt
index 9a7392a..522a82c 100644
--- a/ui/ui-foundation/api/1.0.0-alpha01.txt
+++ b/ui/ui-foundation/api/1.0.0-alpha01.txt
@@ -42,6 +42,27 @@
method public void vertical(int column, kotlin.ranges.IntRange rows = 0 <other> rowCount, androidx.ui.foundation.shape.border.Border border = defaultBorder);
}
+ public final class ScrollerKt {
+ ctor public ScrollerKt();
+ method public static void HorizontalScroller(androidx.ui.foundation.ScrollerPosition scrollerPosition = +memo({
+ <init>()
+}), kotlin.jvm.functions.Function2<? super androidx.ui.core.Px,? super androidx.ui.core.Px,kotlin.Unit> onScrollPositionChanged = { position, _ -> scrollerPosition.value = position }, boolean isScrollable = true, kotlin.jvm.functions.Function0<kotlin.Unit> child);
+ method public static void VerticalScroller(androidx.ui.foundation.ScrollerPosition scrollerPosition = +memo({
+ <init>()
+}), kotlin.jvm.functions.Function2<? super androidx.ui.core.Px,? super androidx.ui.core.Px,kotlin.Unit> onScrollPositionChanged = { position, _ -> scrollerPosition.value = position }, boolean isScrollable = true, kotlin.jvm.functions.Function0<kotlin.Unit> child);
+ }
+
+ public final class ScrollerPosition {
+ ctor public ScrollerPosition();
+ method public androidx.ui.core.Px getValue();
+ method public void scrollBy(androidx.ui.core.Px value);
+ method public void scrollTo(androidx.ui.core.Px value);
+ method public void setValue(androidx.ui.core.Px p);
+ method public void smoothScrollBy(androidx.ui.core.Px value, kotlin.jvm.functions.Function1<? super java.lang.Boolean,kotlin.Unit> onFinished = {});
+ method public void smoothScrollTo(androidx.ui.core.Px value, kotlin.jvm.functions.Function1<? super java.lang.Boolean,kotlin.Unit> onFinished = {});
+ property public final androidx.ui.core.Px value;
+ }
+
public final class SimpleImageKt {
ctor public SimpleImageKt();
method public static void SimpleImage(androidx.ui.painting.Image image, androidx.ui.graphics.Color? tint = null);
@@ -71,13 +92,13 @@
package androidx.ui.foundation.animation {
public final class AnimatedFloatDragController implements androidx.ui.foundation.gestures.DragValueController {
+ ctor public AnimatedFloatDragController(androidx.animation.AnimatedFloat animatedFloat, androidx.ui.foundation.animation.FlingConfig? flingConfig);
ctor public AnimatedFloatDragController(float initialValue, androidx.ui.foundation.animation.FlingConfig? flingConfig);
method public androidx.animation.AnimatedFloat getAnimatedFloat();
method public float getCurrentValue();
method public void onDrag(float target);
method public void onDragEnd(float velocity, kotlin.jvm.functions.Function1<? super java.lang.Float,kotlin.Unit> onValueSettled);
method public void setBounds(float min, float max);
- property public final androidx.animation.AnimatedFloat animatedFloat;
property public float currentValue;
}
@@ -237,9 +258,9 @@
package androidx.ui.foundation.shape.corner {
public abstract class CornerBasedShape implements androidx.ui.engine.geometry.Shape {
- ctor public CornerBasedShape(androidx.ui.foundation.shape.corner.CornerSizes corners);
+ ctor public CornerBasedShape(androidx.ui.foundation.shape.corner.CornerSize topLeft, androidx.ui.foundation.shape.corner.CornerSize topRight, androidx.ui.foundation.shape.corner.CornerSize bottomRight, androidx.ui.foundation.shape.corner.CornerSize bottomLeft);
method public final androidx.ui.engine.geometry.Outline createOutline(androidx.ui.core.PxSize size, androidx.ui.core.Density density);
- method public abstract androidx.ui.engine.geometry.Outline createOutline(androidx.ui.foundation.shape.corner.PxCornerSizes corners, androidx.ui.core.PxSize size);
+ method public abstract androidx.ui.engine.geometry.Outline createOutline(androidx.ui.core.PxSize size, androidx.ui.core.Px topLeft, androidx.ui.core.Px topRight, androidx.ui.core.Px bottomRight, androidx.ui.core.Px bottomLeft);
}
public interface CornerSize {
@@ -255,60 +276,29 @@
method public static androidx.ui.foundation.shape.corner.CornerSize getZeroCornerSize();
}
- public final class CornerSizes {
- ctor public CornerSizes(androidx.ui.foundation.shape.corner.CornerSize topLeft, androidx.ui.foundation.shape.corner.CornerSize topRight, androidx.ui.foundation.shape.corner.CornerSize bottomRight, androidx.ui.foundation.shape.corner.CornerSize bottomLeft);
+ public final class RoundedCornerShape extends androidx.ui.foundation.shape.corner.CornerBasedShape {
+ ctor public RoundedCornerShape(androidx.ui.foundation.shape.corner.CornerSize topLeft, androidx.ui.foundation.shape.corner.CornerSize topRight, androidx.ui.foundation.shape.corner.CornerSize bottomRight, androidx.ui.foundation.shape.corner.CornerSize bottomLeft);
method public androidx.ui.foundation.shape.corner.CornerSize component1();
method public androidx.ui.foundation.shape.corner.CornerSize component2();
method public androidx.ui.foundation.shape.corner.CornerSize component3();
method public androidx.ui.foundation.shape.corner.CornerSize component4();
- method public androidx.ui.foundation.shape.corner.CornerSizes copy(androidx.ui.foundation.shape.corner.CornerSize topLeft, androidx.ui.foundation.shape.corner.CornerSize topRight, androidx.ui.foundation.shape.corner.CornerSize bottomRight, androidx.ui.foundation.shape.corner.CornerSize bottomLeft);
+ method public androidx.ui.foundation.shape.corner.RoundedCornerShape copy(androidx.ui.foundation.shape.corner.CornerSize topLeft, androidx.ui.foundation.shape.corner.CornerSize topRight, androidx.ui.foundation.shape.corner.CornerSize bottomRight, androidx.ui.foundation.shape.corner.CornerSize bottomLeft);
+ method public androidx.ui.engine.geometry.Outline.Rounded createOutline(androidx.ui.core.PxSize size, androidx.ui.core.Px topLeft, androidx.ui.core.Px topRight, androidx.ui.core.Px bottomRight, androidx.ui.core.Px bottomLeft);
method public androidx.ui.foundation.shape.corner.CornerSize getBottomLeft();
method public androidx.ui.foundation.shape.corner.CornerSize getBottomRight();
method public androidx.ui.foundation.shape.corner.CornerSize getTopLeft();
method public androidx.ui.foundation.shape.corner.CornerSize getTopRight();
}
- public final class CornerSizesKt {
- ctor public CornerSizesKt();
- method public static androidx.ui.foundation.shape.corner.CornerSizes CornerSizes(androidx.ui.foundation.shape.corner.CornerSize allCornersSize);
- method public static androidx.ui.foundation.shape.corner.CornerSizes CornerSizes(androidx.ui.core.Dp size);
- method public static androidx.ui.foundation.shape.corner.CornerSizes CornerSizes(androidx.ui.core.Px size);
- method public static androidx.ui.foundation.shape.corner.CornerSizes CornerSizes(int percent);
- method public static androidx.ui.foundation.shape.corner.CornerSizes CornerSizes(androidx.ui.core.Dp topLeft = 0.dp, androidx.ui.core.Dp topRight = 0.dp, androidx.ui.core.Dp bottomRight = 0.dp, androidx.ui.core.Dp bottomLeft = 0.dp);
- method public static androidx.ui.foundation.shape.corner.CornerSizes CornerSizes(androidx.ui.core.Px topLeft = 0.px, androidx.ui.core.Px topRight = 0.px, androidx.ui.core.Px bottomRight = 0.px, androidx.ui.core.Px bottomLeft = 0.px);
- method public static androidx.ui.foundation.shape.corner.CornerSizes CornerSizes(@IntRange(from=0, to=50) int topLeftPercent = 0, @IntRange(from=0, to=50) int topRightPercent = 0, @IntRange(from=0, to=50) int bottomRightPercent = 0, @IntRange(from=0, to=50) int bottomLeftPercent = 0);
- }
-
- public final class PxCornerSizes {
- ctor public PxCornerSizes(androidx.ui.core.Px topLeft, androidx.ui.core.Px topRight, androidx.ui.core.Px bottomRight, androidx.ui.core.Px bottomLeft);
- method public androidx.ui.core.Px component1();
- method public androidx.ui.core.Px component2();
- method public androidx.ui.core.Px component3();
- method public androidx.ui.core.Px component4();
- method public androidx.ui.foundation.shape.corner.PxCornerSizes copy(androidx.ui.core.Px topLeft, androidx.ui.core.Px topRight, androidx.ui.core.Px bottomRight, androidx.ui.core.Px bottomLeft);
- method public androidx.ui.core.Px getBottomLeft();
- method public androidx.ui.core.Px getBottomRight();
- method public androidx.ui.core.Px getTopLeft();
- method public androidx.ui.core.Px getTopRight();
- }
-
- public final class PxCornerSizesKt {
- ctor public PxCornerSizesKt();
- method public static androidx.ui.foundation.shape.corner.PxCornerSizes PxCornerSizes(androidx.ui.foundation.shape.corner.CornerSizes corners, androidx.ui.core.PxSize size, androidx.ui.core.Density density);
- method public static boolean isEmpty(androidx.ui.foundation.shape.corner.PxCornerSizes);
- }
-
- public final class RoundedCornerShape extends androidx.ui.foundation.shape.corner.CornerBasedShape {
- ctor public RoundedCornerShape(androidx.ui.foundation.shape.corner.CornerSizes corners);
- method public androidx.ui.foundation.shape.corner.CornerSizes component1();
- method public androidx.ui.foundation.shape.corner.RoundedCornerShape copy(androidx.ui.foundation.shape.corner.CornerSizes corners);
- method public androidx.ui.engine.geometry.Outline.Rounded createOutline(androidx.ui.foundation.shape.corner.PxCornerSizes corners, androidx.ui.core.PxSize size);
- method public androidx.ui.foundation.shape.corner.CornerSizes getCorners();
- }
-
public final class RoundedCornerShapeKt {
ctor public RoundedCornerShapeKt();
method public static androidx.ui.foundation.shape.corner.RoundedCornerShape RoundedCornerShape(androidx.ui.foundation.shape.corner.CornerSize corner);
+ method public static androidx.ui.foundation.shape.corner.RoundedCornerShape RoundedCornerShape(androidx.ui.core.Dp size);
+ method public static androidx.ui.foundation.shape.corner.RoundedCornerShape RoundedCornerShape(androidx.ui.core.Px size);
+ method public static androidx.ui.foundation.shape.corner.RoundedCornerShape RoundedCornerShape(int percent);
+ method public static androidx.ui.foundation.shape.corner.RoundedCornerShape RoundedCornerShape(androidx.ui.core.Dp topLeft = 0.dp, androidx.ui.core.Dp topRight = 0.dp, androidx.ui.core.Dp bottomRight = 0.dp, androidx.ui.core.Dp bottomLeft = 0.dp);
+ method public static androidx.ui.foundation.shape.corner.RoundedCornerShape RoundedCornerShape(androidx.ui.core.Px topLeft = 0.px, androidx.ui.core.Px topRight = 0.px, androidx.ui.core.Px bottomRight = 0.px, androidx.ui.core.Px bottomLeft = 0.px);
+ method public static androidx.ui.foundation.shape.corner.RoundedCornerShape RoundedCornerShape(@IntRange(from=0, to=50) int topLeftPercent = 0, @IntRange(from=0, to=50) int topRightPercent = 0, @IntRange(from=0, to=50) int bottomRightPercent = 0, @IntRange(from=0, to=50) int bottomLeftPercent = 0);
method public static androidx.ui.foundation.shape.corner.RoundedCornerShape getCircleShape();
}
diff --git a/ui/ui-foundation/api/current.txt b/ui/ui-foundation/api/current.txt
index 9a7392a..522a82c 100644
--- a/ui/ui-foundation/api/current.txt
+++ b/ui/ui-foundation/api/current.txt
@@ -42,6 +42,27 @@
method public void vertical(int column, kotlin.ranges.IntRange rows = 0 <other> rowCount, androidx.ui.foundation.shape.border.Border border = defaultBorder);
}
+ public final class ScrollerKt {
+ ctor public ScrollerKt();
+ method public static void HorizontalScroller(androidx.ui.foundation.ScrollerPosition scrollerPosition = +memo({
+ <init>()
+}), kotlin.jvm.functions.Function2<? super androidx.ui.core.Px,? super androidx.ui.core.Px,kotlin.Unit> onScrollPositionChanged = { position, _ -> scrollerPosition.value = position }, boolean isScrollable = true, kotlin.jvm.functions.Function0<kotlin.Unit> child);
+ method public static void VerticalScroller(androidx.ui.foundation.ScrollerPosition scrollerPosition = +memo({
+ <init>()
+}), kotlin.jvm.functions.Function2<? super androidx.ui.core.Px,? super androidx.ui.core.Px,kotlin.Unit> onScrollPositionChanged = { position, _ -> scrollerPosition.value = position }, boolean isScrollable = true, kotlin.jvm.functions.Function0<kotlin.Unit> child);
+ }
+
+ public final class ScrollerPosition {
+ ctor public ScrollerPosition();
+ method public androidx.ui.core.Px getValue();
+ method public void scrollBy(androidx.ui.core.Px value);
+ method public void scrollTo(androidx.ui.core.Px value);
+ method public void setValue(androidx.ui.core.Px p);
+ method public void smoothScrollBy(androidx.ui.core.Px value, kotlin.jvm.functions.Function1<? super java.lang.Boolean,kotlin.Unit> onFinished = {});
+ method public void smoothScrollTo(androidx.ui.core.Px value, kotlin.jvm.functions.Function1<? super java.lang.Boolean,kotlin.Unit> onFinished = {});
+ property public final androidx.ui.core.Px value;
+ }
+
public final class SimpleImageKt {
ctor public SimpleImageKt();
method public static void SimpleImage(androidx.ui.painting.Image image, androidx.ui.graphics.Color? tint = null);
@@ -71,13 +92,13 @@
package androidx.ui.foundation.animation {
public final class AnimatedFloatDragController implements androidx.ui.foundation.gestures.DragValueController {
+ ctor public AnimatedFloatDragController(androidx.animation.AnimatedFloat animatedFloat, androidx.ui.foundation.animation.FlingConfig? flingConfig);
ctor public AnimatedFloatDragController(float initialValue, androidx.ui.foundation.animation.FlingConfig? flingConfig);
method public androidx.animation.AnimatedFloat getAnimatedFloat();
method public float getCurrentValue();
method public void onDrag(float target);
method public void onDragEnd(float velocity, kotlin.jvm.functions.Function1<? super java.lang.Float,kotlin.Unit> onValueSettled);
method public void setBounds(float min, float max);
- property public final androidx.animation.AnimatedFloat animatedFloat;
property public float currentValue;
}
@@ -237,9 +258,9 @@
package androidx.ui.foundation.shape.corner {
public abstract class CornerBasedShape implements androidx.ui.engine.geometry.Shape {
- ctor public CornerBasedShape(androidx.ui.foundation.shape.corner.CornerSizes corners);
+ ctor public CornerBasedShape(androidx.ui.foundation.shape.corner.CornerSize topLeft, androidx.ui.foundation.shape.corner.CornerSize topRight, androidx.ui.foundation.shape.corner.CornerSize bottomRight, androidx.ui.foundation.shape.corner.CornerSize bottomLeft);
method public final androidx.ui.engine.geometry.Outline createOutline(androidx.ui.core.PxSize size, androidx.ui.core.Density density);
- method public abstract androidx.ui.engine.geometry.Outline createOutline(androidx.ui.foundation.shape.corner.PxCornerSizes corners, androidx.ui.core.PxSize size);
+ method public abstract androidx.ui.engine.geometry.Outline createOutline(androidx.ui.core.PxSize size, androidx.ui.core.Px topLeft, androidx.ui.core.Px topRight, androidx.ui.core.Px bottomRight, androidx.ui.core.Px bottomLeft);
}
public interface CornerSize {
@@ -255,60 +276,29 @@
method public static androidx.ui.foundation.shape.corner.CornerSize getZeroCornerSize();
}
- public final class CornerSizes {
- ctor public CornerSizes(androidx.ui.foundation.shape.corner.CornerSize topLeft, androidx.ui.foundation.shape.corner.CornerSize topRight, androidx.ui.foundation.shape.corner.CornerSize bottomRight, androidx.ui.foundation.shape.corner.CornerSize bottomLeft);
+ public final class RoundedCornerShape extends androidx.ui.foundation.shape.corner.CornerBasedShape {
+ ctor public RoundedCornerShape(androidx.ui.foundation.shape.corner.CornerSize topLeft, androidx.ui.foundation.shape.corner.CornerSize topRight, androidx.ui.foundation.shape.corner.CornerSize bottomRight, androidx.ui.foundation.shape.corner.CornerSize bottomLeft);
method public androidx.ui.foundation.shape.corner.CornerSize component1();
method public androidx.ui.foundation.shape.corner.CornerSize component2();
method public androidx.ui.foundation.shape.corner.CornerSize component3();
method public androidx.ui.foundation.shape.corner.CornerSize component4();
- method public androidx.ui.foundation.shape.corner.CornerSizes copy(androidx.ui.foundation.shape.corner.CornerSize topLeft, androidx.ui.foundation.shape.corner.CornerSize topRight, androidx.ui.foundation.shape.corner.CornerSize bottomRight, androidx.ui.foundation.shape.corner.CornerSize bottomLeft);
+ method public androidx.ui.foundation.shape.corner.RoundedCornerShape copy(androidx.ui.foundation.shape.corner.CornerSize topLeft, androidx.ui.foundation.shape.corner.CornerSize topRight, androidx.ui.foundation.shape.corner.CornerSize bottomRight, androidx.ui.foundation.shape.corner.CornerSize bottomLeft);
+ method public androidx.ui.engine.geometry.Outline.Rounded createOutline(androidx.ui.core.PxSize size, androidx.ui.core.Px topLeft, androidx.ui.core.Px topRight, androidx.ui.core.Px bottomRight, androidx.ui.core.Px bottomLeft);
method public androidx.ui.foundation.shape.corner.CornerSize getBottomLeft();
method public androidx.ui.foundation.shape.corner.CornerSize getBottomRight();
method public androidx.ui.foundation.shape.corner.CornerSize getTopLeft();
method public androidx.ui.foundation.shape.corner.CornerSize getTopRight();
}
- public final class CornerSizesKt {
- ctor public CornerSizesKt();
- method public static androidx.ui.foundation.shape.corner.CornerSizes CornerSizes(androidx.ui.foundation.shape.corner.CornerSize allCornersSize);
- method public static androidx.ui.foundation.shape.corner.CornerSizes CornerSizes(androidx.ui.core.Dp size);
- method public static androidx.ui.foundation.shape.corner.CornerSizes CornerSizes(androidx.ui.core.Px size);
- method public static androidx.ui.foundation.shape.corner.CornerSizes CornerSizes(int percent);
- method public static androidx.ui.foundation.shape.corner.CornerSizes CornerSizes(androidx.ui.core.Dp topLeft = 0.dp, androidx.ui.core.Dp topRight = 0.dp, androidx.ui.core.Dp bottomRight = 0.dp, androidx.ui.core.Dp bottomLeft = 0.dp);
- method public static androidx.ui.foundation.shape.corner.CornerSizes CornerSizes(androidx.ui.core.Px topLeft = 0.px, androidx.ui.core.Px topRight = 0.px, androidx.ui.core.Px bottomRight = 0.px, androidx.ui.core.Px bottomLeft = 0.px);
- method public static androidx.ui.foundation.shape.corner.CornerSizes CornerSizes(@IntRange(from=0, to=50) int topLeftPercent = 0, @IntRange(from=0, to=50) int topRightPercent = 0, @IntRange(from=0, to=50) int bottomRightPercent = 0, @IntRange(from=0, to=50) int bottomLeftPercent = 0);
- }
-
- public final class PxCornerSizes {
- ctor public PxCornerSizes(androidx.ui.core.Px topLeft, androidx.ui.core.Px topRight, androidx.ui.core.Px bottomRight, androidx.ui.core.Px bottomLeft);
- method public androidx.ui.core.Px component1();
- method public androidx.ui.core.Px component2();
- method public androidx.ui.core.Px component3();
- method public androidx.ui.core.Px component4();
- method public androidx.ui.foundation.shape.corner.PxCornerSizes copy(androidx.ui.core.Px topLeft, androidx.ui.core.Px topRight, androidx.ui.core.Px bottomRight, androidx.ui.core.Px bottomLeft);
- method public androidx.ui.core.Px getBottomLeft();
- method public androidx.ui.core.Px getBottomRight();
- method public androidx.ui.core.Px getTopLeft();
- method public androidx.ui.core.Px getTopRight();
- }
-
- public final class PxCornerSizesKt {
- ctor public PxCornerSizesKt();
- method public static androidx.ui.foundation.shape.corner.PxCornerSizes PxCornerSizes(androidx.ui.foundation.shape.corner.CornerSizes corners, androidx.ui.core.PxSize size, androidx.ui.core.Density density);
- method public static boolean isEmpty(androidx.ui.foundation.shape.corner.PxCornerSizes);
- }
-
- public final class RoundedCornerShape extends androidx.ui.foundation.shape.corner.CornerBasedShape {
- ctor public RoundedCornerShape(androidx.ui.foundation.shape.corner.CornerSizes corners);
- method public androidx.ui.foundation.shape.corner.CornerSizes component1();
- method public androidx.ui.foundation.shape.corner.RoundedCornerShape copy(androidx.ui.foundation.shape.corner.CornerSizes corners);
- method public androidx.ui.engine.geometry.Outline.Rounded createOutline(androidx.ui.foundation.shape.corner.PxCornerSizes corners, androidx.ui.core.PxSize size);
- method public androidx.ui.foundation.shape.corner.CornerSizes getCorners();
- }
-
public final class RoundedCornerShapeKt {
ctor public RoundedCornerShapeKt();
method public static androidx.ui.foundation.shape.corner.RoundedCornerShape RoundedCornerShape(androidx.ui.foundation.shape.corner.CornerSize corner);
+ method public static androidx.ui.foundation.shape.corner.RoundedCornerShape RoundedCornerShape(androidx.ui.core.Dp size);
+ method public static androidx.ui.foundation.shape.corner.RoundedCornerShape RoundedCornerShape(androidx.ui.core.Px size);
+ method public static androidx.ui.foundation.shape.corner.RoundedCornerShape RoundedCornerShape(int percent);
+ method public static androidx.ui.foundation.shape.corner.RoundedCornerShape RoundedCornerShape(androidx.ui.core.Dp topLeft = 0.dp, androidx.ui.core.Dp topRight = 0.dp, androidx.ui.core.Dp bottomRight = 0.dp, androidx.ui.core.Dp bottomLeft = 0.dp);
+ method public static androidx.ui.foundation.shape.corner.RoundedCornerShape RoundedCornerShape(androidx.ui.core.Px topLeft = 0.px, androidx.ui.core.Px topRight = 0.px, androidx.ui.core.Px bottomRight = 0.px, androidx.ui.core.Px bottomLeft = 0.px);
+ method public static androidx.ui.foundation.shape.corner.RoundedCornerShape RoundedCornerShape(@IntRange(from=0, to=50) int topLeftPercent = 0, @IntRange(from=0, to=50) int topRightPercent = 0, @IntRange(from=0, to=50) int bottomRightPercent = 0, @IntRange(from=0, to=50) int bottomLeftPercent = 0);
method public static androidx.ui.foundation.shape.corner.RoundedCornerShape getCircleShape();
}
diff --git a/ui/ui-foundation/api/restricted_1.0.0-alpha01.txt b/ui/ui-foundation/api/restricted_1.0.0-alpha01.txt
index 9a7392a..522a82c 100644
--- a/ui/ui-foundation/api/restricted_1.0.0-alpha01.txt
+++ b/ui/ui-foundation/api/restricted_1.0.0-alpha01.txt
@@ -42,6 +42,27 @@
method public void vertical(int column, kotlin.ranges.IntRange rows = 0 <other> rowCount, androidx.ui.foundation.shape.border.Border border = defaultBorder);
}
+ public final class ScrollerKt {
+ ctor public ScrollerKt();
+ method public static void HorizontalScroller(androidx.ui.foundation.ScrollerPosition scrollerPosition = +memo({
+ <init>()
+}), kotlin.jvm.functions.Function2<? super androidx.ui.core.Px,? super androidx.ui.core.Px,kotlin.Unit> onScrollPositionChanged = { position, _ -> scrollerPosition.value = position }, boolean isScrollable = true, kotlin.jvm.functions.Function0<kotlin.Unit> child);
+ method public static void VerticalScroller(androidx.ui.foundation.ScrollerPosition scrollerPosition = +memo({
+ <init>()
+}), kotlin.jvm.functions.Function2<? super androidx.ui.core.Px,? super androidx.ui.core.Px,kotlin.Unit> onScrollPositionChanged = { position, _ -> scrollerPosition.value = position }, boolean isScrollable = true, kotlin.jvm.functions.Function0<kotlin.Unit> child);
+ }
+
+ public final class ScrollerPosition {
+ ctor public ScrollerPosition();
+ method public androidx.ui.core.Px getValue();
+ method public void scrollBy(androidx.ui.core.Px value);
+ method public void scrollTo(androidx.ui.core.Px value);
+ method public void setValue(androidx.ui.core.Px p);
+ method public void smoothScrollBy(androidx.ui.core.Px value, kotlin.jvm.functions.Function1<? super java.lang.Boolean,kotlin.Unit> onFinished = {});
+ method public void smoothScrollTo(androidx.ui.core.Px value, kotlin.jvm.functions.Function1<? super java.lang.Boolean,kotlin.Unit> onFinished = {});
+ property public final androidx.ui.core.Px value;
+ }
+
public final class SimpleImageKt {
ctor public SimpleImageKt();
method public static void SimpleImage(androidx.ui.painting.Image image, androidx.ui.graphics.Color? tint = null);
@@ -71,13 +92,13 @@
package androidx.ui.foundation.animation {
public final class AnimatedFloatDragController implements androidx.ui.foundation.gestures.DragValueController {
+ ctor public AnimatedFloatDragController(androidx.animation.AnimatedFloat animatedFloat, androidx.ui.foundation.animation.FlingConfig? flingConfig);
ctor public AnimatedFloatDragController(float initialValue, androidx.ui.foundation.animation.FlingConfig? flingConfig);
method public androidx.animation.AnimatedFloat getAnimatedFloat();
method public float getCurrentValue();
method public void onDrag(float target);
method public void onDragEnd(float velocity, kotlin.jvm.functions.Function1<? super java.lang.Float,kotlin.Unit> onValueSettled);
method public void setBounds(float min, float max);
- property public final androidx.animation.AnimatedFloat animatedFloat;
property public float currentValue;
}
@@ -237,9 +258,9 @@
package androidx.ui.foundation.shape.corner {
public abstract class CornerBasedShape implements androidx.ui.engine.geometry.Shape {
- ctor public CornerBasedShape(androidx.ui.foundation.shape.corner.CornerSizes corners);
+ ctor public CornerBasedShape(androidx.ui.foundation.shape.corner.CornerSize topLeft, androidx.ui.foundation.shape.corner.CornerSize topRight, androidx.ui.foundation.shape.corner.CornerSize bottomRight, androidx.ui.foundation.shape.corner.CornerSize bottomLeft);
method public final androidx.ui.engine.geometry.Outline createOutline(androidx.ui.core.PxSize size, androidx.ui.core.Density density);
- method public abstract androidx.ui.engine.geometry.Outline createOutline(androidx.ui.foundation.shape.corner.PxCornerSizes corners, androidx.ui.core.PxSize size);
+ method public abstract androidx.ui.engine.geometry.Outline createOutline(androidx.ui.core.PxSize size, androidx.ui.core.Px topLeft, androidx.ui.core.Px topRight, androidx.ui.core.Px bottomRight, androidx.ui.core.Px bottomLeft);
}
public interface CornerSize {
@@ -255,60 +276,29 @@
method public static androidx.ui.foundation.shape.corner.CornerSize getZeroCornerSize();
}
- public final class CornerSizes {
- ctor public CornerSizes(androidx.ui.foundation.shape.corner.CornerSize topLeft, androidx.ui.foundation.shape.corner.CornerSize topRight, androidx.ui.foundation.shape.corner.CornerSize bottomRight, androidx.ui.foundation.shape.corner.CornerSize bottomLeft);
+ public final class RoundedCornerShape extends androidx.ui.foundation.shape.corner.CornerBasedShape {
+ ctor public RoundedCornerShape(androidx.ui.foundation.shape.corner.CornerSize topLeft, androidx.ui.foundation.shape.corner.CornerSize topRight, androidx.ui.foundation.shape.corner.CornerSize bottomRight, androidx.ui.foundation.shape.corner.CornerSize bottomLeft);
method public androidx.ui.foundation.shape.corner.CornerSize component1();
method public androidx.ui.foundation.shape.corner.CornerSize component2();
method public androidx.ui.foundation.shape.corner.CornerSize component3();
method public androidx.ui.foundation.shape.corner.CornerSize component4();
- method public androidx.ui.foundation.shape.corner.CornerSizes copy(androidx.ui.foundation.shape.corner.CornerSize topLeft, androidx.ui.foundation.shape.corner.CornerSize topRight, androidx.ui.foundation.shape.corner.CornerSize bottomRight, androidx.ui.foundation.shape.corner.CornerSize bottomLeft);
+ method public androidx.ui.foundation.shape.corner.RoundedCornerShape copy(androidx.ui.foundation.shape.corner.CornerSize topLeft, androidx.ui.foundation.shape.corner.CornerSize topRight, androidx.ui.foundation.shape.corner.CornerSize bottomRight, androidx.ui.foundation.shape.corner.CornerSize bottomLeft);
+ method public androidx.ui.engine.geometry.Outline.Rounded createOutline(androidx.ui.core.PxSize size, androidx.ui.core.Px topLeft, androidx.ui.core.Px topRight, androidx.ui.core.Px bottomRight, androidx.ui.core.Px bottomLeft);
method public androidx.ui.foundation.shape.corner.CornerSize getBottomLeft();
method public androidx.ui.foundation.shape.corner.CornerSize getBottomRight();
method public androidx.ui.foundation.shape.corner.CornerSize getTopLeft();
method public androidx.ui.foundation.shape.corner.CornerSize getTopRight();
}
- public final class CornerSizesKt {
- ctor public CornerSizesKt();
- method public static androidx.ui.foundation.shape.corner.CornerSizes CornerSizes(androidx.ui.foundation.shape.corner.CornerSize allCornersSize);
- method public static androidx.ui.foundation.shape.corner.CornerSizes CornerSizes(androidx.ui.core.Dp size);
- method public static androidx.ui.foundation.shape.corner.CornerSizes CornerSizes(androidx.ui.core.Px size);
- method public static androidx.ui.foundation.shape.corner.CornerSizes CornerSizes(int percent);
- method public static androidx.ui.foundation.shape.corner.CornerSizes CornerSizes(androidx.ui.core.Dp topLeft = 0.dp, androidx.ui.core.Dp topRight = 0.dp, androidx.ui.core.Dp bottomRight = 0.dp, androidx.ui.core.Dp bottomLeft = 0.dp);
- method public static androidx.ui.foundation.shape.corner.CornerSizes CornerSizes(androidx.ui.core.Px topLeft = 0.px, androidx.ui.core.Px topRight = 0.px, androidx.ui.core.Px bottomRight = 0.px, androidx.ui.core.Px bottomLeft = 0.px);
- method public static androidx.ui.foundation.shape.corner.CornerSizes CornerSizes(@IntRange(from=0, to=50) int topLeftPercent = 0, @IntRange(from=0, to=50) int topRightPercent = 0, @IntRange(from=0, to=50) int bottomRightPercent = 0, @IntRange(from=0, to=50) int bottomLeftPercent = 0);
- }
-
- public final class PxCornerSizes {
- ctor public PxCornerSizes(androidx.ui.core.Px topLeft, androidx.ui.core.Px topRight, androidx.ui.core.Px bottomRight, androidx.ui.core.Px bottomLeft);
- method public androidx.ui.core.Px component1();
- method public androidx.ui.core.Px component2();
- method public androidx.ui.core.Px component3();
- method public androidx.ui.core.Px component4();
- method public androidx.ui.foundation.shape.corner.PxCornerSizes copy(androidx.ui.core.Px topLeft, androidx.ui.core.Px topRight, androidx.ui.core.Px bottomRight, androidx.ui.core.Px bottomLeft);
- method public androidx.ui.core.Px getBottomLeft();
- method public androidx.ui.core.Px getBottomRight();
- method public androidx.ui.core.Px getTopLeft();
- method public androidx.ui.core.Px getTopRight();
- }
-
- public final class PxCornerSizesKt {
- ctor public PxCornerSizesKt();
- method public static androidx.ui.foundation.shape.corner.PxCornerSizes PxCornerSizes(androidx.ui.foundation.shape.corner.CornerSizes corners, androidx.ui.core.PxSize size, androidx.ui.core.Density density);
- method public static boolean isEmpty(androidx.ui.foundation.shape.corner.PxCornerSizes);
- }
-
- public final class RoundedCornerShape extends androidx.ui.foundation.shape.corner.CornerBasedShape {
- ctor public RoundedCornerShape(androidx.ui.foundation.shape.corner.CornerSizes corners);
- method public androidx.ui.foundation.shape.corner.CornerSizes component1();
- method public androidx.ui.foundation.shape.corner.RoundedCornerShape copy(androidx.ui.foundation.shape.corner.CornerSizes corners);
- method public androidx.ui.engine.geometry.Outline.Rounded createOutline(androidx.ui.foundation.shape.corner.PxCornerSizes corners, androidx.ui.core.PxSize size);
- method public androidx.ui.foundation.shape.corner.CornerSizes getCorners();
- }
-
public final class RoundedCornerShapeKt {
ctor public RoundedCornerShapeKt();
method public static androidx.ui.foundation.shape.corner.RoundedCornerShape RoundedCornerShape(androidx.ui.foundation.shape.corner.CornerSize corner);
+ method public static androidx.ui.foundation.shape.corner.RoundedCornerShape RoundedCornerShape(androidx.ui.core.Dp size);
+ method public static androidx.ui.foundation.shape.corner.RoundedCornerShape RoundedCornerShape(androidx.ui.core.Px size);
+ method public static androidx.ui.foundation.shape.corner.RoundedCornerShape RoundedCornerShape(int percent);
+ method public static androidx.ui.foundation.shape.corner.RoundedCornerShape RoundedCornerShape(androidx.ui.core.Dp topLeft = 0.dp, androidx.ui.core.Dp topRight = 0.dp, androidx.ui.core.Dp bottomRight = 0.dp, androidx.ui.core.Dp bottomLeft = 0.dp);
+ method public static androidx.ui.foundation.shape.corner.RoundedCornerShape RoundedCornerShape(androidx.ui.core.Px topLeft = 0.px, androidx.ui.core.Px topRight = 0.px, androidx.ui.core.Px bottomRight = 0.px, androidx.ui.core.Px bottomLeft = 0.px);
+ method public static androidx.ui.foundation.shape.corner.RoundedCornerShape RoundedCornerShape(@IntRange(from=0, to=50) int topLeftPercent = 0, @IntRange(from=0, to=50) int topRightPercent = 0, @IntRange(from=0, to=50) int bottomRightPercent = 0, @IntRange(from=0, to=50) int bottomLeftPercent = 0);
method public static androidx.ui.foundation.shape.corner.RoundedCornerShape getCircleShape();
}
diff --git a/ui/ui-foundation/api/restricted_current.txt b/ui/ui-foundation/api/restricted_current.txt
index 9a7392a..522a82c 100644
--- a/ui/ui-foundation/api/restricted_current.txt
+++ b/ui/ui-foundation/api/restricted_current.txt
@@ -42,6 +42,27 @@
method public void vertical(int column, kotlin.ranges.IntRange rows = 0 <other> rowCount, androidx.ui.foundation.shape.border.Border border = defaultBorder);
}
+ public final class ScrollerKt {
+ ctor public ScrollerKt();
+ method public static void HorizontalScroller(androidx.ui.foundation.ScrollerPosition scrollerPosition = +memo({
+ <init>()
+}), kotlin.jvm.functions.Function2<? super androidx.ui.core.Px,? super androidx.ui.core.Px,kotlin.Unit> onScrollPositionChanged = { position, _ -> scrollerPosition.value = position }, boolean isScrollable = true, kotlin.jvm.functions.Function0<kotlin.Unit> child);
+ method public static void VerticalScroller(androidx.ui.foundation.ScrollerPosition scrollerPosition = +memo({
+ <init>()
+}), kotlin.jvm.functions.Function2<? super androidx.ui.core.Px,? super androidx.ui.core.Px,kotlin.Unit> onScrollPositionChanged = { position, _ -> scrollerPosition.value = position }, boolean isScrollable = true, kotlin.jvm.functions.Function0<kotlin.Unit> child);
+ }
+
+ public final class ScrollerPosition {
+ ctor public ScrollerPosition();
+ method public androidx.ui.core.Px getValue();
+ method public void scrollBy(androidx.ui.core.Px value);
+ method public void scrollTo(androidx.ui.core.Px value);
+ method public void setValue(androidx.ui.core.Px p);
+ method public void smoothScrollBy(androidx.ui.core.Px value, kotlin.jvm.functions.Function1<? super java.lang.Boolean,kotlin.Unit> onFinished = {});
+ method public void smoothScrollTo(androidx.ui.core.Px value, kotlin.jvm.functions.Function1<? super java.lang.Boolean,kotlin.Unit> onFinished = {});
+ property public final androidx.ui.core.Px value;
+ }
+
public final class SimpleImageKt {
ctor public SimpleImageKt();
method public static void SimpleImage(androidx.ui.painting.Image image, androidx.ui.graphics.Color? tint = null);
@@ -71,13 +92,13 @@
package androidx.ui.foundation.animation {
public final class AnimatedFloatDragController implements androidx.ui.foundation.gestures.DragValueController {
+ ctor public AnimatedFloatDragController(androidx.animation.AnimatedFloat animatedFloat, androidx.ui.foundation.animation.FlingConfig? flingConfig);
ctor public AnimatedFloatDragController(float initialValue, androidx.ui.foundation.animation.FlingConfig? flingConfig);
method public androidx.animation.AnimatedFloat getAnimatedFloat();
method public float getCurrentValue();
method public void onDrag(float target);
method public void onDragEnd(float velocity, kotlin.jvm.functions.Function1<? super java.lang.Float,kotlin.Unit> onValueSettled);
method public void setBounds(float min, float max);
- property public final androidx.animation.AnimatedFloat animatedFloat;
property public float currentValue;
}
@@ -237,9 +258,9 @@
package androidx.ui.foundation.shape.corner {
public abstract class CornerBasedShape implements androidx.ui.engine.geometry.Shape {
- ctor public CornerBasedShape(androidx.ui.foundation.shape.corner.CornerSizes corners);
+ ctor public CornerBasedShape(androidx.ui.foundation.shape.corner.CornerSize topLeft, androidx.ui.foundation.shape.corner.CornerSize topRight, androidx.ui.foundation.shape.corner.CornerSize bottomRight, androidx.ui.foundation.shape.corner.CornerSize bottomLeft);
method public final androidx.ui.engine.geometry.Outline createOutline(androidx.ui.core.PxSize size, androidx.ui.core.Density density);
- method public abstract androidx.ui.engine.geometry.Outline createOutline(androidx.ui.foundation.shape.corner.PxCornerSizes corners, androidx.ui.core.PxSize size);
+ method public abstract androidx.ui.engine.geometry.Outline createOutline(androidx.ui.core.PxSize size, androidx.ui.core.Px topLeft, androidx.ui.core.Px topRight, androidx.ui.core.Px bottomRight, androidx.ui.core.Px bottomLeft);
}
public interface CornerSize {
@@ -255,60 +276,29 @@
method public static androidx.ui.foundation.shape.corner.CornerSize getZeroCornerSize();
}
- public final class CornerSizes {
- ctor public CornerSizes(androidx.ui.foundation.shape.corner.CornerSize topLeft, androidx.ui.foundation.shape.corner.CornerSize topRight, androidx.ui.foundation.shape.corner.CornerSize bottomRight, androidx.ui.foundation.shape.corner.CornerSize bottomLeft);
+ public final class RoundedCornerShape extends androidx.ui.foundation.shape.corner.CornerBasedShape {
+ ctor public RoundedCornerShape(androidx.ui.foundation.shape.corner.CornerSize topLeft, androidx.ui.foundation.shape.corner.CornerSize topRight, androidx.ui.foundation.shape.corner.CornerSize bottomRight, androidx.ui.foundation.shape.corner.CornerSize bottomLeft);
method public androidx.ui.foundation.shape.corner.CornerSize component1();
method public androidx.ui.foundation.shape.corner.CornerSize component2();
method public androidx.ui.foundation.shape.corner.CornerSize component3();
method public androidx.ui.foundation.shape.corner.CornerSize component4();
- method public androidx.ui.foundation.shape.corner.CornerSizes copy(androidx.ui.foundation.shape.corner.CornerSize topLeft, androidx.ui.foundation.shape.corner.CornerSize topRight, androidx.ui.foundation.shape.corner.CornerSize bottomRight, androidx.ui.foundation.shape.corner.CornerSize bottomLeft);
+ method public androidx.ui.foundation.shape.corner.RoundedCornerShape copy(androidx.ui.foundation.shape.corner.CornerSize topLeft, androidx.ui.foundation.shape.corner.CornerSize topRight, androidx.ui.foundation.shape.corner.CornerSize bottomRight, androidx.ui.foundation.shape.corner.CornerSize bottomLeft);
+ method public androidx.ui.engine.geometry.Outline.Rounded createOutline(androidx.ui.core.PxSize size, androidx.ui.core.Px topLeft, androidx.ui.core.Px topRight, androidx.ui.core.Px bottomRight, androidx.ui.core.Px bottomLeft);
method public androidx.ui.foundation.shape.corner.CornerSize getBottomLeft();
method public androidx.ui.foundation.shape.corner.CornerSize getBottomRight();
method public androidx.ui.foundation.shape.corner.CornerSize getTopLeft();
method public androidx.ui.foundation.shape.corner.CornerSize getTopRight();
}
- public final class CornerSizesKt {
- ctor public CornerSizesKt();
- method public static androidx.ui.foundation.shape.corner.CornerSizes CornerSizes(androidx.ui.foundation.shape.corner.CornerSize allCornersSize);
- method public static androidx.ui.foundation.shape.corner.CornerSizes CornerSizes(androidx.ui.core.Dp size);
- method public static androidx.ui.foundation.shape.corner.CornerSizes CornerSizes(androidx.ui.core.Px size);
- method public static androidx.ui.foundation.shape.corner.CornerSizes CornerSizes(int percent);
- method public static androidx.ui.foundation.shape.corner.CornerSizes CornerSizes(androidx.ui.core.Dp topLeft = 0.dp, androidx.ui.core.Dp topRight = 0.dp, androidx.ui.core.Dp bottomRight = 0.dp, androidx.ui.core.Dp bottomLeft = 0.dp);
- method public static androidx.ui.foundation.shape.corner.CornerSizes CornerSizes(androidx.ui.core.Px topLeft = 0.px, androidx.ui.core.Px topRight = 0.px, androidx.ui.core.Px bottomRight = 0.px, androidx.ui.core.Px bottomLeft = 0.px);
- method public static androidx.ui.foundation.shape.corner.CornerSizes CornerSizes(@IntRange(from=0, to=50) int topLeftPercent = 0, @IntRange(from=0, to=50) int topRightPercent = 0, @IntRange(from=0, to=50) int bottomRightPercent = 0, @IntRange(from=0, to=50) int bottomLeftPercent = 0);
- }
-
- public final class PxCornerSizes {
- ctor public PxCornerSizes(androidx.ui.core.Px topLeft, androidx.ui.core.Px topRight, androidx.ui.core.Px bottomRight, androidx.ui.core.Px bottomLeft);
- method public androidx.ui.core.Px component1();
- method public androidx.ui.core.Px component2();
- method public androidx.ui.core.Px component3();
- method public androidx.ui.core.Px component4();
- method public androidx.ui.foundation.shape.corner.PxCornerSizes copy(androidx.ui.core.Px topLeft, androidx.ui.core.Px topRight, androidx.ui.core.Px bottomRight, androidx.ui.core.Px bottomLeft);
- method public androidx.ui.core.Px getBottomLeft();
- method public androidx.ui.core.Px getBottomRight();
- method public androidx.ui.core.Px getTopLeft();
- method public androidx.ui.core.Px getTopRight();
- }
-
- public final class PxCornerSizesKt {
- ctor public PxCornerSizesKt();
- method public static androidx.ui.foundation.shape.corner.PxCornerSizes PxCornerSizes(androidx.ui.foundation.shape.corner.CornerSizes corners, androidx.ui.core.PxSize size, androidx.ui.core.Density density);
- method public static boolean isEmpty(androidx.ui.foundation.shape.corner.PxCornerSizes);
- }
-
- public final class RoundedCornerShape extends androidx.ui.foundation.shape.corner.CornerBasedShape {
- ctor public RoundedCornerShape(androidx.ui.foundation.shape.corner.CornerSizes corners);
- method public androidx.ui.foundation.shape.corner.CornerSizes component1();
- method public androidx.ui.foundation.shape.corner.RoundedCornerShape copy(androidx.ui.foundation.shape.corner.CornerSizes corners);
- method public androidx.ui.engine.geometry.Outline.Rounded createOutline(androidx.ui.foundation.shape.corner.PxCornerSizes corners, androidx.ui.core.PxSize size);
- method public androidx.ui.foundation.shape.corner.CornerSizes getCorners();
- }
-
public final class RoundedCornerShapeKt {
ctor public RoundedCornerShapeKt();
method public static androidx.ui.foundation.shape.corner.RoundedCornerShape RoundedCornerShape(androidx.ui.foundation.shape.corner.CornerSize corner);
+ method public static androidx.ui.foundation.shape.corner.RoundedCornerShape RoundedCornerShape(androidx.ui.core.Dp size);
+ method public static androidx.ui.foundation.shape.corner.RoundedCornerShape RoundedCornerShape(androidx.ui.core.Px size);
+ method public static androidx.ui.foundation.shape.corner.RoundedCornerShape RoundedCornerShape(int percent);
+ method public static androidx.ui.foundation.shape.corner.RoundedCornerShape RoundedCornerShape(androidx.ui.core.Dp topLeft = 0.dp, androidx.ui.core.Dp topRight = 0.dp, androidx.ui.core.Dp bottomRight = 0.dp, androidx.ui.core.Dp bottomLeft = 0.dp);
+ method public static androidx.ui.foundation.shape.corner.RoundedCornerShape RoundedCornerShape(androidx.ui.core.Px topLeft = 0.px, androidx.ui.core.Px topRight = 0.px, androidx.ui.core.Px bottomRight = 0.px, androidx.ui.core.Px bottomLeft = 0.px);
+ method public static androidx.ui.foundation.shape.corner.RoundedCornerShape RoundedCornerShape(@IntRange(from=0, to=50) int topLeftPercent = 0, @IntRange(from=0, to=50) int topRightPercent = 0, @IntRange(from=0, to=50) int bottomRightPercent = 0, @IntRange(from=0, to=50) int bottomLeftPercent = 0);
method public static androidx.ui.foundation.shape.corner.RoundedCornerShape getCircleShape();
}
diff --git a/ui/ui-foundation/integration-tests/foundation-demos/src/main/AndroidManifest.xml b/ui/ui-foundation/integration-tests/foundation-demos/src/main/AndroidManifest.xml
index 4609982..2bdca6b 100644
--- a/ui/ui-foundation/integration-tests/foundation-demos/src/main/AndroidManifest.xml
+++ b/ui/ui-foundation/integration-tests/foundation-demos/src/main/AndroidManifest.xml
@@ -21,11 +21,27 @@
<application>
<activity android:name=".AnimatedDraggableActivity"
- android:configChanges="orientation|screenSize"
- android:label="Foundation/AnimatedDraggable">
+ android:configChanges="orientation|screenSize"
+ android:label="Foundation/AnimatedDraggable">
<intent-filter>
- <action android:name="android.intent.action.MAIN" />
- <category android:name="androidx.ui.demos.SAMPLE_CODE" />
+ <action android:name="android.intent.action.MAIN"/>
+ <category android:name="androidx.ui.demos.SAMPLE_CODE"/>
+ </intent-filter>
+ </activity>
+ <activity android:name=".VerticalScrollerActivity"
+ android:configChanges="orientation|screenSize"
+ android:label="Foundation/VerticalScroller">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN"/>
+ <category android:name="androidx.ui.demos.SAMPLE_CODE"/>
+ </intent-filter>
+ </activity>
+ <activity android:name=".HorizontalScrollerActivity"
+ android:configChanges="orientation|screenSize"
+ android:label="Foundation/HorizontalScroller with controls">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN"/>
+ <category android:name="androidx.ui.demos.SAMPLE_CODE"/>
</intent-filter>
</activity>
diff --git a/ui/ui-foundation/integration-tests/foundation-demos/src/main/java/androidx/ui/foundation/demos/HorizontalScrollerActivity.kt b/ui/ui-foundation/integration-tests/foundation-demos/src/main/java/androidx/ui/foundation/demos/HorizontalScrollerActivity.kt
new file mode 100644
index 0000000..6b4d4b9
--- /dev/null
+++ b/ui/ui-foundation/integration-tests/foundation-demos/src/main/java/androidx/ui/foundation/demos/HorizontalScrollerActivity.kt
@@ -0,0 +1,36 @@
+/*
+ * 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.foundation.demos
+
+import android.app.Activity
+import android.os.Bundle
+import androidx.compose.composer
+import androidx.ui.core.setContent
+import androidx.ui.foundation.samples.ControlledHorizontalScrollerSample
+import androidx.ui.layout.Wrap
+
+class HorizontalScrollerActivity : Activity() {
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ setContent {
+ Wrap {
+ ControlledHorizontalScrollerSample()
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/ui/ui-foundation/integration-tests/foundation-demos/src/main/java/androidx/ui/foundation/demos/VerticalScrollerActivity.kt b/ui/ui-foundation/integration-tests/foundation-demos/src/main/java/androidx/ui/foundation/demos/VerticalScrollerActivity.kt
new file mode 100644
index 0000000..defa1f8
--- /dev/null
+++ b/ui/ui-foundation/integration-tests/foundation-demos/src/main/java/androidx/ui/foundation/demos/VerticalScrollerActivity.kt
@@ -0,0 +1,36 @@
+/*
+ * 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.foundation.demos
+
+import android.app.Activity
+import android.os.Bundle
+import androidx.compose.composer
+import androidx.ui.core.setContent
+import androidx.ui.foundation.samples.VerticalScrollerSample
+import androidx.ui.layout.Wrap
+
+class VerticalScrollerActivity : Activity() {
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ setContent {
+ Wrap {
+ VerticalScrollerSample()
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/ui/ui-foundation/integration-tests/samples/src/main/java/androidx/ui/foundation/samples/ScrollerSamples.kt b/ui/ui-foundation/integration-tests/samples/src/main/java/androidx/ui/foundation/samples/ScrollerSamples.kt
new file mode 100644
index 0000000..30d8e63
--- /dev/null
+++ b/ui/ui-foundation/integration-tests/samples/src/main/java/androidx/ui/foundation/samples/ScrollerSamples.kt
@@ -0,0 +1,199 @@
+/*
+ * 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.foundation.samples
+
+import android.util.Log
+import androidx.annotation.Sampled
+import androidx.compose.Composable
+import androidx.compose.State
+import androidx.compose.composer
+import androidx.compose.memo
+import androidx.compose.state
+import androidx.compose.unaryPlus
+import androidx.ui.core.Draw
+import androidx.ui.core.PxSize
+import androidx.ui.core.Text
+import androidx.ui.core.dp
+import androidx.ui.core.px
+import androidx.ui.core.setContent
+import androidx.ui.core.sp
+import androidx.ui.core.toRect
+import androidx.ui.foundation.Clickable
+import androidx.ui.foundation.ColoredRect
+import androidx.ui.foundation.HorizontalScroller
+import androidx.ui.foundation.VerticalScroller
+import androidx.ui.graphics.Color
+import androidx.ui.layout.Column
+import androidx.ui.layout.Padding
+import androidx.ui.layout.Row
+import androidx.ui.foundation.ScrollerPosition
+import androidx.ui.foundation.shape.DrawShape
+import androidx.ui.foundation.shape.RectangleShape
+import androidx.ui.layout.Alignment
+import androidx.ui.layout.Container
+import androidx.ui.layout.EdgeInsets
+import androidx.ui.layout.FlexColumn
+import androidx.ui.layout.HeightSpacer
+import androidx.ui.layout.Stack
+import androidx.ui.layout.Table
+import androidx.ui.layout.Wrap
+import androidx.ui.painting.Canvas
+import androidx.ui.painting.Paint
+import androidx.ui.text.ParagraphStyle
+import androidx.ui.text.TextStyle
+
+private val colors = listOf(
+ Color(0xFFffd7d7.toInt()),
+ Color(0xFFffe9d6.toInt()),
+ Color(0xFFfffbd0.toInt()),
+ Color(0xFFe3ffd9.toInt()),
+ Color(0xFFd0fff8.toInt())
+)
+
+private val phrases = listOf(
+ "Easy As Pie",
+ "Wouldn't Harm a Fly",
+ "No-Brainer",
+ "Keep On Truckin'",
+ "An Arm and a Leg",
+ "Down To Earth",
+ "Under the Weather",
+ "Up In Arms",
+ "Cup Of Joe",
+ "Not the Sharpest Tool in the Shed",
+ "Ring Any Bells?",
+ "Son of a Gun",
+ "Hard Pill to Swallow",
+ "Close But No Cigar",
+ "Beating a Dead Horse",
+ "If You Can't Stand the Heat, Get Out of the Kitchen",
+ "Cut To The Chase",
+ "Heads Up",
+ "Goody Two-Shoes",
+ "Fish Out Of Water",
+ "Cry Over Spilt Milk",
+ "Elephant in the Room",
+ "There's No I in Team",
+ "Poke Fun At",
+ "Talk the Talk",
+ "Know the Ropes",
+ "Fool's Gold",
+ "It's Not Brain Surgery",
+ "Fight Fire With Fire",
+ "Go For Broke"
+)
+
+@Sampled
+@Composable
+fun VerticalScrollerSample() {
+ val style = TextStyle(fontSize = 30.sp)
+ // Scroller will be clipped to this padding
+ Padding(padding = 10.dp) {
+ VerticalScroller {
+ Column {
+ phrases.forEach { phrase ->
+ Text(text = phrase, style = style)
+ }
+ }
+ }
+ }
+}
+
+@Sampled
+@Composable
+fun SimpleHorizontalScrollerSample() {
+ HorizontalScroller {
+ Row {
+ repeat(100) { index ->
+ Square(index)
+ }
+ }
+ }
+}
+
+@Sampled
+@Composable
+fun ControlledHorizontalScrollerSample() {
+ // Create and own ScrollerPosition to call `smoothScrollTo` later
+ val position = +memo { ScrollerPosition() }
+ val scrollable = +state { true }
+ Column {
+ HorizontalScroller(scrollerPosition = position, isScrollable = scrollable.value) {
+ Row {
+ repeat(1000) { index ->
+ Square(index)
+ }
+ }
+ }
+ // Controls that will call `smoothScrollTo`, `scrollTo` or toggle `scrollable` state
+ ScrollControl(position, scrollable)
+ }
+}
+
+@Composable
+private fun Square(index: Int) {
+ Container(width = 75.dp, height = 200.dp) {
+ DrawShape(RectangleShape, colors[index % colors.size])
+ Text(index.toString())
+ }
+}
+
+@Composable
+private fun ScrollControl(position: ScrollerPosition, scrollable: State<Boolean>) {
+ Padding(top = 20.dp) {
+ Table(3, childAlignment = Alignment.Center) {
+ tableRow {
+ Text("Scroll")
+ SquareButton("< -", Color.Red) {
+ position.scrollTo(position.value - 1000.px)
+ }
+ SquareButton("--- >", Color.Green) {
+ position.scrollBy(10000.px)
+ }
+ }
+ tableRow {
+ Text("Smooth Scroll")
+ SquareButton("< -", Color.Red) {
+ position.smoothScrollTo(position.value - 1000.px)
+ }
+ SquareButton("--- >", Color.Green) {
+ position.smoothScrollBy(10000.px)
+ }
+ }
+ tableRow {
+ SquareButton("Scroll: ${scrollable.value}") {
+ scrollable.value = !scrollable.value
+ }
+ // empty container to fill table
+ Container { }
+ Container { }
+ }
+ }
+ }
+}
+
+@Composable
+private fun SquareButton(text: String, color: Color = Color.LightGray, onClick: () -> Unit) {
+ Clickable(onClick = onClick) {
+ Padding(5.dp) {
+ Container(height = 60.dp, width = 120.dp) {
+ DrawShape(RectangleShape, color)
+ Text(text, style = TextStyle(fontSize = 20.sp))
+ }
+ }
+ }
+}
diff --git a/ui/ui-foundation/src/androidTest/AndroidManifest.xml b/ui/ui-foundation/src/androidTest/AndroidManifest.xml
index 8c0fd1c..bacf210 100644
--- a/ui/ui-foundation/src/androidTest/AndroidManifest.xml
+++ b/ui/ui-foundation/src/androidTest/AndroidManifest.xml
@@ -14,8 +14,14 @@
~ See the License for the specific language governing permissions and
~ limitations under the License.
-->
-<manifest package="androidx.ui.foundation" xmlns:android="http://schemas.android.com/apk/res/android">
- <application>
+<manifest package="androidx.ui.foundation"
+ xmlns:tools="http://schemas.android.com/tools"
+ xmlns:android="http://schemas.android.com/apk/res/android">
+ <application
+ android:requestLegacyExternalStorage="true"
+ android:debuggable="false"
+ tools:ignore="HardcodedDebugMode"
+ tools:replace="android:debuggable">
<activity
android:name="androidx.ui.test.android.DefaultTestActivity"
android:theme="@style/TestTheme"/>
diff --git a/ui/ui-foundation/src/androidTest/java/androidx/ui/foundation/ClickableTest.kt b/ui/ui-foundation/src/androidTest/java/androidx/ui/foundation/ClickableTest.kt
index 2f7e1c3..4f790c7 100644
--- a/ui/ui-foundation/src/androidTest/java/androidx/ui/foundation/ClickableTest.kt
+++ b/ui/ui-foundation/src/androidTest/java/androidx/ui/foundation/ClickableTest.kt
@@ -21,6 +21,8 @@
import androidx.ui.core.TestTag
import androidx.ui.core.Text
import androidx.ui.layout.Center
+import androidx.ui.test.assertHasClickAction
+import androidx.ui.test.assertHasNoClickAction
import androidx.ui.test.assertSemanticsIsEqualTo
import androidx.ui.test.createComposeRule
import androidx.ui.test.createFullSemantics
@@ -57,6 +59,7 @@
isEnabled = true
)
)
+ .assertHasClickAction()
}
@Test
@@ -77,6 +80,7 @@
isEnabled = false
)
)
+ .assertHasNoClickAction()
}
@Test
diff --git a/ui/ui-layout/src/androidTest/java/androidx/ui/layout/test/ScrollerTest.kt b/ui/ui-foundation/src/androidTest/java/androidx/ui/foundation/ScrollerTest.kt
similarity index 65%
rename from ui/ui-layout/src/androidTest/java/androidx/ui/layout/test/ScrollerTest.kt
rename to ui/ui-foundation/src/androidTest/java/androidx/ui/foundation/ScrollerTest.kt
index b15be36..9f78d2c 100644
--- a/ui/ui-layout/src/androidTest/java/androidx/ui/layout/test/ScrollerTest.kt
+++ b/ui/ui-foundation/src/androidTest/java/androidx/ui/foundation/ScrollerTest.kt
@@ -13,11 +13,15 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package androidx.ui.layout.test
+package androidx.ui.foundation
+import android.app.Activity
import android.graphics.Bitmap
import android.os.Build
+import android.os.Handler
import android.view.PixelCopy
+import android.view.View
+import android.view.ViewGroup
import android.view.ViewTreeObserver
import androidx.test.filters.SmallTest
import androidx.ui.core.Draw
@@ -34,20 +38,21 @@
import androidx.ui.layout.Container
import androidx.ui.layout.CrossAxisAlignment
import androidx.ui.layout.DpConstraints
-import androidx.ui.layout.ScrollerPosition
-import androidx.ui.layout.VerticalScroller
import androidx.ui.graphics.Color
import androidx.ui.painting.Paint
import androidx.ui.painting.PaintingStyle
import androidx.compose.composer
import androidx.test.filters.SdkSuppress
+import androidx.ui.core.AndroidCraneView
import androidx.ui.core.setContent
-import androidx.ui.layout.HorizontalScroller
import androidx.ui.layout.Row
+import androidx.ui.test.android.AndroidComposeTestRule
+import androidx.ui.test.createComposeRule
import org.junit.Assert.assertEquals
import org.junit.Assert.assertNotEquals
import org.junit.Assert.assertTrue
import org.junit.Before
+import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
import org.junit.runners.JUnit4
@@ -56,7 +61,16 @@
@SmallTest
@RunWith(JUnit4::class)
-class ScrollerTest : LayoutTest() {
+class ScrollerTest {
+
+ @get:Rule
+ val composeTestRule = createComposeRule()
+
+ // TODO(malkov/pavlis) : some tests here require activity access as we need
+ // to take screen's bitmap, abstract it better
+ val activity
+ get() = (composeTestRule as AndroidComposeTestRule).activityTestRule.activity
+
val colors = listOf(
Color(alpha = 0xFF, red = 0xFF, green = 0, blue = 0),
Color(alpha = 0xFF, red = 0xFF, green = 0xA5, blue = 0),
@@ -69,10 +83,14 @@
)
var drawLatch = CountDownLatch(1)
+ lateinit var handler: Handler
@Before
fun setupDrawLatch() {
drawLatch = CountDownLatch(1)
+ composeTestRule.runOnUiThread {
+ handler = Handler()
+ }
}
@SdkSuppress(minSdkVersion = Build.VERSION_CODES.O)
@@ -99,8 +117,6 @@
val changeListener = ScrollerChangeListener(scrollerPosition)
composeVerticalScroller(scrollerPosition, changeListener, height = 30.ipx)
- changeListener.waitForChange()
-
validateVerticalScroller(0, 30)
// The 'draw' method will no longer be called because only the position
@@ -112,13 +128,13 @@
latch.countDown()
}
}
- runOnUiThread {
+ composeTestRule.runOnUiThread {
activity.window.decorView.viewTreeObserver.addOnDrawListener(onDrawListener)
assertEquals(10.px, changeListener.maxPosition)
- scrollerPosition.position = 10.px
+ scrollerPosition.value = 10.px
}
assertTrue(latch.await(1, TimeUnit.SECONDS))
- runOnUiThread {
+ composeTestRule.runOnUiThread {
activity.window.decorView.viewTreeObserver.removeOnDrawListener(onDrawListener)
}
validateVerticalScroller(10, 30)
@@ -148,8 +164,6 @@
val changeListener = ScrollerChangeListener(scrollerPosition)
composeHorizontalScroller(scrollerPosition, changeListener, width = 30.ipx)
- changeListener.waitForChange()
-
validateHorizontalScroller(0, 30)
// The 'draw' method will no longer be called because only the position
@@ -161,13 +175,13 @@
latch.countDown()
}
}
- runOnUiThread {
+ composeTestRule.runOnUiThread {
activity.window.decorView.viewTreeObserver.addOnDrawListener(onDrawListener)
assertEquals(10.px, changeListener.maxPosition)
- scrollerPosition.position = 10.px
+ scrollerPosition.value = 10.px
}
assertTrue(latch.await(1, TimeUnit.SECONDS))
- runOnUiThread {
+ composeTestRule.runOnUiThread {
activity.window.decorView.viewTreeObserver.removeOnDrawListener(onDrawListener)
}
validateHorizontalScroller(10, 30)
@@ -175,95 +189,89 @@
private fun composeVerticalScroller(
scrollerPosition: ScrollerPosition = ScrollerPosition(),
- onScrollChanged: (position: Px, maxPosition: Px) -> Unit = { position, _ ->
- scrollerPosition.position = position
+ onScrollChanged: (position: Px, max: Px) -> Unit = { position, _ ->
+ scrollerPosition.value = position
},
height: IntPx = 40.ipx
) {
// We assume that the height of the device is more than 45 px
- withDensity(density) {
+ withDensity(composeTestRule.density) {
val constraints = DpConstraints.tightConstraints(45.px.toDp(), height.toDp())
- val runnable: Runnable = object : Runnable {
- override fun run() {
- activity.setContent {
- Align(alignment = Alignment.TopLeft) {
- ConstrainedBox(constraints = constraints) {
- VerticalScroller(
- scrollerPosition = scrollerPosition,
- onScrollChanged = onScrollChanged
- ) {
- Column(crossAxisAlignment = CrossAxisAlignment.Start) {
- colors.forEach { color ->
- Container(
- height = 5.px.toDp(),
- width = 45.px.toDp()
- ) {
- Draw { canvas, parentSize ->
- val paint = Paint()
- paint.color = color
- paint.style = PaintingStyle.fill
- canvas.drawRect(parentSize.toRect(), paint)
- }
+ composeTestRule.runOnUiThread {
+ activity.setContent {
+ Align(alignment = Alignment.TopLeft) {
+ ConstrainedBox(constraints = constraints) {
+ VerticalScroller(
+ scrollerPosition = scrollerPosition,
+ onScrollPositionChanged = onScrollChanged
+ ) {
+ Column(crossAxisAlignment = CrossAxisAlignment.Start) {
+ colors.forEach { color ->
+ Container(
+ height = 5.px.toDp(),
+ width = 45.px.toDp()
+ ) {
+ Draw { canvas, parentSize ->
+ val paint = Paint()
+ paint.color = color
+ paint.style = PaintingStyle.fill
+ canvas.drawRect(parentSize.toRect(), paint)
}
}
}
}
- Draw { _, _ ->
- drawLatch.countDown()
- }
+ }
+ Draw { _, _ ->
+ drawLatch.countDown()
}
}
}
}
}
- activityTestRule.runOnUiThread(runnable)
}
}
private fun composeHorizontalScroller(
scrollerPosition: ScrollerPosition = ScrollerPosition(),
- onScrollChanged: (position: Px, maxPosition: Px) -> Unit = { position, _ ->
- scrollerPosition.position = position
+ onScrollChanged: (position: Px, max: Px) -> Unit = { position, _ ->
+ scrollerPosition.value = position
},
width: IntPx = 40.ipx
) {
// We assume that the height of the device is more than 45 px
- withDensity(density) {
+ withDensity(composeTestRule.density) {
val constraints = DpConstraints.tightConstraints(width.toDp(), 45.px.toDp())
- val runnable: Runnable = object : Runnable {
- override fun run() {
- activity.setContent {
- Align(alignment = Alignment.TopLeft) {
- ConstrainedBox(constraints = constraints) {
- HorizontalScroller(
- scrollerPosition = scrollerPosition,
- onScrollChanged = onScrollChanged
- ) {
- Row(crossAxisAlignment = CrossAxisAlignment.Start) {
- colors.forEach { color ->
- Container(
- width = 5.px.toDp(),
- height = 45.px.toDp()
- ) {
- Draw { canvas, parentSize ->
- val paint = Paint()
- paint.color = color
- paint.style = PaintingStyle.fill
- canvas.drawRect(parentSize.toRect(), paint)
- }
+ composeTestRule.runOnUiThread {
+ activity.setContent {
+ Align(alignment = Alignment.TopLeft) {
+ ConstrainedBox(constraints = constraints) {
+ HorizontalScroller(
+ scrollerPosition = scrollerPosition,
+ onScrollPositionChanged = onScrollChanged
+ ) {
+ Row(crossAxisAlignment = CrossAxisAlignment.Start) {
+ colors.forEach { color ->
+ Container(
+ width = 5.px.toDp(),
+ height = 45.px.toDp()
+ ) {
+ Draw { canvas, parentSize ->
+ val paint = Paint()
+ paint.color = color
+ paint.style = PaintingStyle.fill
+ canvas.drawRect(parentSize.toRect(), paint)
}
}
}
}
- Draw { _, _ ->
- drawLatch.countDown()
- }
+ }
+ Draw { _, _ ->
+ drawLatch.countDown()
}
}
}
}
}
- activityTestRule.runOnUiThread(runnable)
}
}
@@ -315,16 +323,6 @@
}
}
- // We only need this because IR compiler doesn't like converting lambdas to Runnables
- private fun runOnUiThread(block: () -> Unit) {
- @Suppress("ObjectLiteralToLambda") val runnable: Runnable = object : Runnable {
- override fun run() {
- block()
- }
- }
- activityTestRule.runOnUiThread(runnable)
- }
-
private fun waitAndScreenShot(): Bitmap {
val view = findAndroidCraneView()
waitForDraw(view)
@@ -365,7 +363,7 @@
changeCalls++
lock.notify()
}
- scrollerPosition.position = position
+ scrollerPosition.value = position
this.maxPosition = maxPosition
}
@@ -379,4 +377,42 @@
}
}
}
+
+ // TODO(malkov): ALL below is copypaste from LayoutTest as this test in ui-foundation now
+
+ internal fun findAndroidCraneView(): AndroidCraneView {
+ val contentViewGroup = activity.findViewById<ViewGroup>(android.R.id.content)
+ return findAndroidCraneView(contentViewGroup)!!
+ }
+
+ internal fun findAndroidCraneView(parent: ViewGroup): AndroidCraneView? {
+ for (index in 0 until parent.childCount) {
+ val child = parent.getChildAt(index)
+ if (child is AndroidCraneView) {
+ return child
+ } else if (child is ViewGroup) {
+ val craneView = findAndroidCraneView(child)
+ if (craneView != null) {
+ return craneView
+ }
+ }
+ }
+ return null
+ }
+
+ internal fun waitForDraw(view: View) {
+ val viewDrawLatch = CountDownLatch(1)
+ val listener = object : ViewTreeObserver.OnDrawListener {
+ override fun onDraw() {
+ viewDrawLatch.countDown()
+ }
+ }
+ view.post(object : Runnable {
+ override fun run() {
+ view.viewTreeObserver.addOnDrawListener(listener)
+ view.invalidate()
+ }
+ })
+ assertTrue(viewDrawLatch.await(1, TimeUnit.SECONDS))
+ }
}
diff --git a/ui/ui-foundation/src/androidTest/java/androidx/ui/foundation/shape/corner/RoundedCornerShapeTest.kt b/ui/ui-foundation/src/androidTest/java/androidx/ui/foundation/shape/corner/RoundedCornerShapeTest.kt
index afba564..b9eb9d1 100644
--- a/ui/ui-foundation/src/androidTest/java/androidx/ui/foundation/shape/corner/RoundedCornerShapeTest.kt
+++ b/ui/ui-foundation/src/androidTest/java/androidx/ui/foundation/shape/corner/RoundedCornerShapeTest.kt
@@ -40,7 +40,7 @@
@Test
fun roundedUniformCorners() {
- val rounded = RoundedCornerShape(CornerSizes(25))
+ val rounded = RoundedCornerShape(25)
val expectedRadius = Radius.circular(25f)
val outline = rounded.toOutline() as Outline.Rounded
@@ -57,14 +57,7 @@
val radius2 = 22f
val radius3 = 32f
val radius4 = 42f
- val rounded = RoundedCornerShape(
- CornerSizes(
- CornerSize(radius1.px),
- CornerSize(radius2.px),
- CornerSize(radius3.px),
- CornerSize(radius4.px)
- )
- )
+ val rounded = RoundedCornerShape(radius1.px, radius2.px, radius3.px, radius4.px)
val outline = rounded.toOutline() as Outline.Rounded
assertThat(outline.rrect).isEqualTo(
@@ -80,8 +73,8 @@
@Test
fun roundedCornerShapesAreEquals() {
- assertThat(RoundedCornerShape(CornerSizes(12.dp)))
- .isEqualTo(RoundedCornerShape(CornerSizes(12.dp)))
+ assertThat(RoundedCornerShape(12.dp))
+ .isEqualTo(RoundedCornerShape(12.dp))
}
private fun Shape.toOutline() = createOutline(size, density)
diff --git a/ui/ui-foundation/src/main/java/androidx/ui/foundation/Clickable.kt b/ui/ui-foundation/src/main/java/androidx/ui/foundation/Clickable.kt
index 66473034..fabf791 100644
--- a/ui/ui-foundation/src/main/java/androidx/ui/foundation/Clickable.kt
+++ b/ui/ui-foundation/src/main/java/androidx/ui/foundation/Clickable.kt
@@ -29,7 +29,7 @@
*
* @sample androidx.ui.foundation.samples.ClickableSample
*
- * @param onClick will be called when user clicked on the button. The button will not be
+ * @param onClick will be called when user clicked on the button. The children will not be
* clickable when it is null.
* @param consumeDownOnStart true means [PressReleasedGestureDetector] should consume
* down events. Provide false if you have some visual feedback like Ripples,
diff --git a/ui/ui-foundation/src/main/java/androidx/ui/foundation/Scroller.kt b/ui/ui-foundation/src/main/java/androidx/ui/foundation/Scroller.kt
new file mode 100644
index 0000000..3849680
--- /dev/null
+++ b/ui/ui-foundation/src/main/java/androidx/ui/foundation/Scroller.kt
@@ -0,0 +1,310 @@
+/*
+ * 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.foundation
+
+import androidx.animation.AnimatedFloat
+import androidx.animation.ExponentialDecay
+import androidx.animation.ValueHolder
+import androidx.compose.Children
+import androidx.compose.Composable
+import androidx.compose.composer
+import androidx.compose.Model
+import androidx.compose.memo
+import androidx.compose.state
+import androidx.compose.unaryPlus
+import androidx.ui.core.Clip
+import androidx.ui.core.Constraints
+import androidx.ui.core.IntPx
+import androidx.ui.core.Layout
+import androidx.ui.core.Px
+import androidx.ui.core.RepaintBoundary
+import androidx.ui.core.gesture.PressGestureDetector
+import androidx.ui.core.ipx
+import androidx.ui.core.min
+import androidx.ui.core.px
+import androidx.ui.core.round
+import androidx.ui.core.toPx
+import androidx.ui.foundation.animation.AnimatedFloatDragController
+import androidx.ui.foundation.animation.FlingConfig
+import androidx.ui.foundation.gestures.DragDirection
+import androidx.ui.foundation.gestures.DragValueController
+import androidx.ui.foundation.gestures.Draggable
+import androidx.ui.foundation.shape.RectangleShape
+import androidx.ui.layout.Constraints
+import androidx.ui.layout.Container
+import androidx.ui.lerp
+
+/**
+ * This is the state of a [VerticalScroller] and [HorizontalScroller] that
+ * allows the developer to change the scroll position.
+ * [value] must be between `0` and `maxPosition` in `onScrollPositionChanged`'s `maxPosition`
+ * parameter.
+ */
+@Model
+class ScrollerPosition {
+
+ /**
+ * The amount of scrolling, between `0` and `maxPosition` in `onScrollPositionChanged`'s
+ * `maxPosition` parameter.
+ */
+ var value: Px = 0.px
+
+ /**
+ * Smooth scroll to position in pixels
+ *
+ * @param value target value to smooth scroll to
+ */
+ // TODO (malkov/tianliu) : think about allowing to scroll with custom animation timings/curves
+ fun smoothScrollTo(value: Px, onFinished: (isCancelled: Boolean) -> Unit = {}) {
+ controller.animatedFloat.animateTo(-value.value, onFinished)
+ }
+
+ /**
+ * Smooth scroll by some amount of pixels
+ *
+ * @param value delta to scroll by
+ */
+ fun smoothScrollBy(value: Px, onFinished: (isCancelled: Boolean) -> Unit = {}) {
+ smoothScrollTo(this.value + value, onFinished)
+ }
+
+ /**
+ * Instantly jump to position in pixels
+ *
+ * @param value target value to jump to
+ */
+ fun scrollTo(value: Px) {
+ controller.onDrag(-value.value)
+ }
+
+ /**
+ * Instantly jump by some amount of pixels
+ *
+ * @param value delta to jump by
+ */
+ fun scrollBy(value: Px) {
+ scrollTo(this.value + value)
+ }
+
+ // TODO (malkov/tianliu): Open this for customization
+ private val flingConfig = FlingConfig(
+ decayAnimation = ExponentialDecay(
+ frictionMultiplier = ScrollerDefaultFriction,
+ absVelocityThreshold = ScrollerVelocityThreshold
+ )
+ )
+
+ internal val controller =
+ ScrollerDragValueController({ lh.lambda.invoke(-it) }, flingConfig)
+
+ // This is needed to take instant value we're currently dragging
+ // and avoid reading @Model var field
+ internal val instantValue
+ get() = -controller.currentValue
+
+ // This is needed to avoid var (read of which will cause unnecessary recompose in Scroller)
+ internal val lh = LambdaHolder { value = it.px }
+}
+
+/**
+ * A container that composes all of its contents and lays it out, fitting the width of the child.
+ * If the child's height is less than the [Constraints.maxHeight], the child's height is used,
+ * or the [Constraints.maxHeight] otherwise. If the contents don't fit the height, the drag gesture
+ * allows scrolling its content vertically. The contents of the VerticalScroller are clipped to
+ * the VerticalScroller's bounds.
+ *
+ * @sample androidx.ui.foundation.samples.VerticalScrollerSample
+ *
+ * @param scrollerPosition state of this Scroller that holds current scroll position and provides
+ * user with useful methods like smooth scrolling
+ * @param onScrollPositionChanged callback to be invoked when scroll position is about to be
+ * changed or max bound of scrolling has changed
+ * @param isScrollable param to enabled or disable touch input scrolling, default is true
+ */
+@Composable
+fun VerticalScroller(
+ scrollerPosition: ScrollerPosition = +memo { ScrollerPosition() },
+ onScrollPositionChanged: (position: Px, maxPosition: Px) -> Unit = { position, _ ->
+ scrollerPosition.value = position
+ },
+ isScrollable: Boolean = true,
+ @Children child: @Composable() () -> Unit
+) {
+ Scroller(scrollerPosition, onScrollPositionChanged, true, isScrollable, child)
+}
+
+/**
+ * A container that composes all of its contents and lays it out, fitting the height of the child.
+ * If the child's width is less than the [Constraints.maxWidth], the child's width is used,
+ * or the [Constraints.maxWidth] otherwise. If the contents don't fit the width, the drag gesture
+ * allows scrolling its content horizontally. The contents of the HorizontalScroller are clipped to
+ * the HorizontalScroller's bounds.
+ *
+ * @sample androidx.ui.foundation.samples.SimpleHorizontalScrollerSample
+ *
+ * If you want to control scrolling position from the code, e.g smooth scroll to position,
+ * you must own memorized instance of [ScrollerPosition] and then use it to call `scrollTo...`
+ * functions on it. Same tactic can be applied to the [VerticalScroller]
+ *
+ * @sample androidx.ui.foundation.samples.ControlledHorizontalScrollerSample
+ *
+ * @param scrollerPosition state of this Scroller that holds current scroll position and provides
+ * user with useful methods like smooth scrolling
+ * @param onScrollPositionChanged callback to be invoked when scroll position is about to be
+ * changed or max bound of scrolling has changed
+ * @param isScrollable param to enabled or disable touch input scrolling, default is true
+ */
+@Composable
+fun HorizontalScroller(
+ scrollerPosition: ScrollerPosition = +memo { ScrollerPosition() },
+ onScrollPositionChanged: (position: Px, maxPosition: Px) -> Unit = { position, _ ->
+ scrollerPosition.value = position
+ },
+ isScrollable: Boolean = true,
+ @Children child: @Composable() () -> Unit
+) {
+ Scroller(scrollerPosition, onScrollPositionChanged, false, isScrollable, child)
+}
+
+@Composable
+private fun Scroller(
+ scrollerPosition: ScrollerPosition,
+ onScrollPositionChanged: (position: Px, maxPosition: Px) -> Unit,
+ isVertical: Boolean,
+ isScrollable: Boolean,
+ @Children child: @Composable() () -> Unit
+) {
+ val maxPosition = +state { Px.Infinity }
+ val direction = if (isVertical) DragDirection.Vertical else DragDirection.Horizontal
+ val controller = +memo(isScrollable) {
+ if (isScrollable) {
+ scrollerPosition.controller
+ } else {
+ StubController(scrollerPosition.instantValue).also {
+ scrollerPosition.scrollTo(scrollerPosition.instantValue.px)
+ }
+ }
+ }
+ scrollerPosition.lh.lambda = { onScrollPositionChanged(it.px, maxPosition.value) }
+ PressGestureDetector(onPress = { scrollerPosition.scrollTo(scrollerPosition.value) }) {
+ Draggable(
+ dragDirection = direction,
+ minValue = -maxPosition.value.value,
+ maxValue = 0f,
+ valueController = controller
+ ) {
+ ScrollerLayout(
+ scrollerPosition = scrollerPosition,
+ maxPosition = maxPosition.value,
+ onMaxPositionChanged = {
+ maxPosition.value = it
+ onScrollPositionChanged(scrollerPosition.value, it)
+ },
+ isVertical = isVertical,
+ child = child
+ )
+ }
+ }
+}
+
+@Composable
+private fun ScrollerLayout(
+ scrollerPosition: ScrollerPosition,
+ maxPosition: Px,
+ onMaxPositionChanged: (Px) -> Unit,
+ isVertical: Boolean,
+ child: @Composable() () -> Unit
+) {
+ Layout(children = {
+ Clip(RectangleShape) {
+ Container {
+ RepaintBoundary {
+ child()
+ }
+ }
+ }
+ }) { measurables, constraints ->
+ if (measurables.size > 1) {
+ throw IllegalStateException("Only one child is allowed in a VerticalScroller")
+ }
+ val childConstraints = constraints.copy(
+ maxHeight = if (isVertical) IntPx.Infinity else constraints.maxHeight,
+ maxWidth = if (isVertical) constraints.maxWidth else IntPx.Infinity
+ )
+ val childMeasurable = measurables.firstOrNull()
+ val placeable = childMeasurable?.measure(childConstraints)
+ val width: IntPx
+ val height: IntPx
+ if (placeable == null) {
+ width = constraints.minWidth
+ height = constraints.minHeight
+ } else {
+ width = min(placeable.width, constraints.maxWidth)
+ height = min(placeable.height, constraints.maxHeight)
+ }
+ layout(width, height) {
+ val childHeight = placeable?.height?.toPx() ?: 0.px
+ val childWidth = placeable?.width?.toPx() ?: 0.px
+ val scrollHeight = childHeight - height.toPx()
+ val scrollWidth = childWidth - width.toPx()
+ val side = if (isVertical) scrollHeight else scrollWidth
+ if (side != 0.px && side != maxPosition) {
+ onMaxPositionChanged(side)
+ }
+ val xOffset = if (isVertical) 0.ipx else -scrollerPosition.value.round()
+ val yOffset = if (isVertical) -scrollerPosition.value.round() else 0.ipx
+ placeable?.place(xOffset, yOffset)
+ }
+ }
+}
+
+private fun ScrollerDragValueController(
+ onValueChanged: (Float) -> Unit,
+ flingConfig: FlingConfig? = null
+) = AnimatedFloatDragController(
+ AnimatedFloat(ScrollPositionValueHolder(0f, onValueChanged)),
+ flingConfig
+)
+
+private class ScrollPositionValueHolder(
+ var current: Float,
+ val onValueChanged: (Float) -> Unit
+) : ValueHolder<Float> {
+ override val interpolator: (start: Float, end: Float, fraction: Float) -> Float = ::lerp
+ override var value: Float
+ get() = current
+ set(value) {
+ current = value
+ onValueChanged(value)
+ }
+}
+
+private fun StubController(value: Float) = object : DragValueController {
+ override val currentValue: Float
+ get() = value
+
+ override fun onDrag(target: Float) {}
+
+ override fun onDragEnd(velocity: Float, onValueSettled: (Float) -> Unit) {}
+
+ override fun setBounds(min: Float, max: Float) {}
+}
+
+internal data class LambdaHolder(var lambda: (Float) -> Unit)
+
+private val ScrollerDefaultFriction = 0.35f
+private val ScrollerVelocityThreshold = 1000f
\ No newline at end of file
diff --git a/ui/ui-foundation/src/main/java/androidx/ui/foundation/animation/AnimatedFloatDragController.kt b/ui/ui-foundation/src/main/java/androidx/ui/foundation/animation/AnimatedFloatDragController.kt
index c47f27e..8198e44 100644
--- a/ui/ui-foundation/src/main/java/androidx/ui/foundation/animation/AnimatedFloatDragController.kt
+++ b/ui/ui-foundation/src/main/java/androidx/ui/foundation/animation/AnimatedFloatDragController.kt
@@ -29,16 +29,24 @@
* It makes it possible to have animation support for [Draggable] composable
* as well as to have fling animation after drag has ended, which is defined by [FlingConfig]
*
- * @param initialValue initial value for AnimatedFloat to set it up
+ * @param animatedFloat instance of AnimatedFloat to control
* @param flingConfig sets behavior of the fling after drag has ended.
* Default is null, which means no fling will occur no matter the velocity
*/
class AnimatedFloatDragController(
- initialValue: Float,
+ val animatedFloat: AnimatedFloat,
private val flingConfig: FlingConfig? = null
) : DragValueController {
- val animatedFloat = AnimatedFloat(AnimValueHolder(initialValue, ::lerp))
+ /**
+ * Construct controller that creates and owns AnimatedFloat instance
+ *
+ * @param initialValue initial value for AnimatedFloat to set it up
+ * @param flingConfig sets behavior of the fling after drag has ended.
+ * Default is null, which means no fling will occur no matter the velocity
+ */
+ constructor(initialValue: Float, flingConfig: FlingConfig?)
+ : this(AnimatedFloat(AnimValueHolder(initialValue, ::lerp)), flingConfig)
override val currentValue
get() = animatedFloat.value
diff --git a/ui/ui-foundation/src/main/java/androidx/ui/foundation/shape/corner/CornerBasedShape.kt b/ui/ui-foundation/src/main/java/androidx/ui/foundation/shape/corner/CornerBasedShape.kt
index 7145a6d..89ec2be 100644
--- a/ui/ui-foundation/src/main/java/androidx/ui/foundation/shape/corner/CornerBasedShape.kt
+++ b/ui/ui-foundation/src/main/java/androidx/ui/foundation/shape/corner/CornerBasedShape.kt
@@ -17,36 +17,59 @@
package androidx.ui.foundation.shape.corner
import androidx.ui.core.Density
+import androidx.ui.core.Px
import androidx.ui.core.PxSize
+import androidx.ui.core.px
import androidx.ui.core.toRect
import androidx.ui.engine.geometry.Outline
import androidx.ui.engine.geometry.Shape
/**
- * Base class for [Shape]s defined by [CornerSizes].
+ * Base class for [Shape]s defined by four [CornerSize]s.
*
- * @see RoundedCornerShape for an example of the usage
+ * @see RoundedCornerShape for an example of the usage.
*
- * @param corners define all four corner sizes
+ * @param topLeft a size of the top left corner
+ * @param topRight a size of the top right corner
+ * @param bottomRight a size of the bottom left corner
+ * @param bottomLeft a size of the bottom right corner
*/
abstract class CornerBasedShape(
- private val corners: CornerSizes
+ private val topLeft: CornerSize,
+ private val topRight: CornerSize,
+ private val bottomRight: CornerSize,
+ private val bottomLeft: CornerSize
) : Shape {
final override fun createOutline(size: PxSize, density: Density): Outline {
- val corners = PxCornerSizes(corners, size, density)
- return if (corners.isEmpty()) {
+ val topLeft = topLeft.toPx(size, density)
+ val topRight = topRight.toPx(size, density)
+ val bottomRight = bottomRight.toPx(size, density)
+ val bottomLeft = bottomLeft.toPx(size, density)
+ require(topLeft >= 0.px && topRight >= 0.px && bottomRight >= 0.px && bottomLeft >= 0.px) {
+ "Corner size in Px can't be negative!"
+ }
+ return if (topLeft + topRight + bottomLeft + bottomRight == 0.px) {
Outline.Rectangle(size.toRect())
} else {
- createOutline(corners, size)
+ createOutline(size, topLeft, topRight, bottomRight, bottomLeft)
}
}
/**
- * @param corners the resolved sizes of all the four corners in pixels.
* @param size the size of the shape boundary.
+ * @param topLeft the resolved size of the top left corner
+ * @param topRight the resolved size for the top right corner
+ * @param bottomRight the resolved size for the bottom left corner
+ * @param bottomLeft the resolved size for the bottom right corner
*
* @return [Outline] of this shape for the given [size].
*/
- abstract fun createOutline(corners: PxCornerSizes, size: PxSize): Outline
+ abstract fun createOutline(
+ size: PxSize,
+ topLeft: Px,
+ topRight: Px,
+ bottomRight: Px,
+ bottomLeft: Px
+ ): Outline
}
diff --git a/ui/ui-foundation/src/main/java/androidx/ui/foundation/shape/corner/CornerSizes.kt b/ui/ui-foundation/src/main/java/androidx/ui/foundation/shape/corner/CornerSizes.kt
deleted file mode 100644
index b689b08..0000000
--- a/ui/ui-foundation/src/main/java/androidx/ui/foundation/shape/corner/CornerSizes.kt
+++ /dev/null
@@ -1,108 +0,0 @@
-/*
- * 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.foundation.shape.corner
-
-import androidx.annotation.IntRange
-import androidx.ui.core.Dp
-import androidx.ui.core.Px
-import androidx.ui.core.dp
-import androidx.ui.core.px
-
-/**
- * Contains sizes of all four corner sizes for a shape.
- *
- * @param topLeft a size for the top left corner
- * @param topRight a size for the top right corner
- * @param bottomRight a size for the bottom left corner
- * @param bottomLeft a size for the bottom right corner
- */
-data class CornerSizes(
- val topLeft: CornerSize,
- val topRight: CornerSize,
- val bottomRight: CornerSize,
- val bottomLeft: CornerSize
-)
-
-/**
- * Creates [CornerSizes] with the same size applied for all four corners.
- */
-/*inline*/ fun CornerSizes(allCornersSize: CornerSize) = CornerSizes(
- allCornersSize,
- allCornersSize,
- allCornersSize,
- allCornersSize
-)
-
-/**
- * Creates [CornerSizes] with the same size applied for all four corners.
- */
-/*inline*/ fun CornerSizes(size: Dp) = CornerSizes(CornerSize(size))
-
-/**
- * Creates [CornerSizes] with the same size applied for all four corners.
- */
-/*inline*/ fun CornerSizes(size: Px) = CornerSizes(CornerSize(size))
-
-/**
- * Creates [CornerSizes] with the same size applied for all four corners.
- */
-/*inline*/ fun CornerSizes(percent: Int) = CornerSizes(CornerSize(percent))
-
-/**
- * Creates [CornerSizes] with sizes defined by [Dp].
- */
-/*inline*/ fun CornerSizes(
- topLeft: Dp = 0.dp,
- topRight: Dp = 0.dp,
- bottomRight: Dp = 0.dp,
- bottomLeft: Dp = 0.dp
-) = CornerSizes(
- CornerSize(topLeft),
- CornerSize(topRight),
- CornerSize(bottomRight),
- CornerSize(bottomLeft)
-)
-
-/**
- * Creates [CornerSizes] with sizes defined by [Px].
- */
-/*inline*/ fun CornerSizes(
- topLeft: Px = 0.px,
- topRight: Px = 0.px,
- bottomRight: Px = 0.px,
- bottomLeft: Px = 0.px
-) = CornerSizes(
- CornerSize(topLeft),
- CornerSize(topRight),
- CornerSize(bottomRight),
- CornerSize(bottomLeft)
-)
-
-/**
- * Creates [CornerSizes] with sizes defined by percents of the shape's smaller side.
- */
-/*inline*/ fun CornerSizes(
- @IntRange(from = 0, to = 50) topLeftPercent: Int = 0,
- @IntRange(from = 0, to = 50) topRightPercent: Int = 0,
- @IntRange(from = 0, to = 50) bottomRightPercent: Int = 0,
- @IntRange(from = 0, to = 50) bottomLeftPercent: Int = 0
-) = CornerSizes(
- CornerSize(topLeftPercent),
- CornerSize(topRightPercent),
- CornerSize(bottomRightPercent),
- CornerSize(bottomLeftPercent)
-)
diff --git a/ui/ui-foundation/src/main/java/androidx/ui/foundation/shape/corner/PxCornerSizes.kt b/ui/ui-foundation/src/main/java/androidx/ui/foundation/shape/corner/PxCornerSizes.kt
deleted file mode 100644
index f6d1b58..0000000
--- a/ui/ui-foundation/src/main/java/androidx/ui/foundation/shape/corner/PxCornerSizes.kt
+++ /dev/null
@@ -1,69 +0,0 @@
-/*
- * 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.foundation.shape.corner
-
-import androidx.ui.core.Density
-import androidx.ui.core.Px
-import androidx.ui.core.PxSize
-import androidx.ui.core.px
-
-/**
- * Contains sizes of all four corner sizes for a shape resolved in Pixels
- *
- * @param topLeft a size for the top left corner
- * @param topRight a size for the top right corner
- * @param bottomRight a size for the bottom left corner
- * @param bottomLeft a size for the bottom right corner
- */
-data class PxCornerSizes(
- val topLeft: Px,
- val topRight: Px,
- val bottomRight: Px,
- val bottomLeft: Px
-) {
- init {
- if (topLeft < 0.px || topRight < 0.px || bottomRight < 0.px || bottomLeft < 0.px) {
- throw IllegalArgumentException("Corner size in Px can't be negative!")
- }
- }
-}
-
-/**
- * @return true if all the sizes are equals to 0 pixels.
- */
-/*inline*/ fun PxCornerSizes.isEmpty() =
- topLeft + topRight + bottomLeft + bottomRight == 0.px
-
-/**
- * @param corners define all four corner sizes
- * @param size the size of the shape
- * @param density the current density of the screen.
- *
- * @return resolved [PxCornerSizes].
- */
-/*inline*/ fun PxCornerSizes(
- corners: CornerSizes,
- size: PxSize,
- density: Density
-): PxCornerSizes = with(corners) {
- PxCornerSizes(
- topLeft = topLeft.toPx(size, density),
- topRight = topRight.toPx(size, density),
- bottomRight = bottomRight.toPx(size, density),
- bottomLeft = bottomLeft.toPx(size, density)
- )
-}
diff --git a/ui/ui-foundation/src/main/java/androidx/ui/foundation/shape/corner/RoundedCornerShape.kt b/ui/ui-foundation/src/main/java/androidx/ui/foundation/shape/corner/RoundedCornerShape.kt
index acbfd2d8..4c9c81f 100644
--- a/ui/ui-foundation/src/main/java/androidx/ui/foundation/shape/corner/RoundedCornerShape.kt
+++ b/ui/ui-foundation/src/main/java/androidx/ui/foundation/shape/corner/RoundedCornerShape.kt
@@ -16,8 +16,12 @@
package androidx.ui.foundation.shape.corner
+import androidx.annotation.IntRange
+import androidx.ui.core.Dp
import androidx.ui.core.Px
import androidx.ui.core.PxSize
+import androidx.ui.core.dp
+import androidx.ui.core.px
import androidx.ui.core.toRect
import androidx.ui.engine.geometry.Outline
import androidx.ui.engine.geometry.RRect
@@ -27,20 +31,31 @@
/**
* A shape describing the rectangle with rounded corners.
*
- * @param corners define all four corner sizes
+ * @param topLeft a size of the top left corner
+ * @param topRight a size of the top right corner
+ * @param bottomRight a size of the bottom left corner
+ * @param bottomLeft a size of the bottom right corner
*/
data class RoundedCornerShape(
- val corners: CornerSizes
-) : CornerBasedShape(corners) {
+ val topLeft: CornerSize,
+ val topRight: CornerSize,
+ val bottomRight: CornerSize,
+ val bottomLeft: CornerSize
+) : CornerBasedShape(topLeft, topRight, bottomRight, bottomLeft) {
- override fun createOutline(corners: PxCornerSizes, size: PxSize) =
- Outline.Rounded(
+ override fun createOutline(
+ size: PxSize,
+ topLeft: Px,
+ topRight: Px,
+ bottomRight: Px,
+ bottomLeft: Px
+ ) = Outline.Rounded(
RRect(
rect = size.toRect(),
- topLeft = corners.topLeft.toRadius(),
- topRight = corners.topRight.toRadius(),
- bottomRight = corners.bottomRight.toRadius(),
- bottomLeft = corners.bottomLeft.toRadius()
+ topLeft = topLeft.toRadius(),
+ topRight = topRight.toRadius(),
+ bottomRight = bottomRight.toRadius(),
+ bottomLeft = bottomLeft.toRadius()
)
)
@@ -48,14 +63,72 @@
}
/**
- * A shape describing the rectangle with rounded corners.
- *
- * @param corner size to apply for all four corners
- */
-/*inline*/ fun RoundedCornerShape(corner: CornerSize) =
- RoundedCornerShape(CornerSizes(corner))
-
-/**
* Circular [Shape] with all the corners sized as the 50 percent of the shape size.
*/
-val CircleShape = RoundedCornerShape(CornerSizes(CornerSize(50)))
+val CircleShape = RoundedCornerShape(50)
+
+/**
+ * Creates [RoundedCornerShape] with the same size applied for all four corners.
+ */
+/*inline*/ fun RoundedCornerShape(corner: CornerSize) =
+ RoundedCornerShape(corner, corner, corner, corner)
+
+/**
+ * Creates [RoundedCornerShape] with the same size applied for all four corners.
+ */
+/*inline*/ fun RoundedCornerShape(size: Dp) = RoundedCornerShape(CornerSize(size))
+
+/**
+ * Creates [RoundedCornerShape] with the same size applied for all four corners.
+ */
+/*inline*/ fun RoundedCornerShape(size: Px) = RoundedCornerShape(CornerSize(size))
+
+/**
+ * Creates [RoundedCornerShape] with the same size applied for all four corners.
+ */
+/*inline*/ fun RoundedCornerShape(percent: Int) = RoundedCornerShape(CornerSize(percent))
+
+/**
+ * Creates [RoundedCornerShape] with sizes defined by [Dp].
+ */
+/*inline*/ fun RoundedCornerShape(
+ topLeft: Dp = 0.dp,
+ topRight: Dp = 0.dp,
+ bottomRight: Dp = 0.dp,
+ bottomLeft: Dp = 0.dp
+) = RoundedCornerShape(
+ CornerSize(topLeft),
+ CornerSize(topRight),
+ CornerSize(bottomRight),
+ CornerSize(bottomLeft)
+)
+
+/**
+ * Creates [RoundedCornerShape] with sizes defined by [Px].
+ */
+/*inline*/ fun RoundedCornerShape(
+ topLeft: Px = 0.px,
+ topRight: Px = 0.px,
+ bottomRight: Px = 0.px,
+ bottomLeft: Px = 0.px
+) = RoundedCornerShape(
+ CornerSize(topLeft),
+ CornerSize(topRight),
+ CornerSize(bottomRight),
+ CornerSize(bottomLeft)
+)
+
+/**
+ * Creates [RoundedCornerShape] with sizes defined by percents of the shape's smaller side.
+ */
+/*inline*/ fun RoundedCornerShape(
+ @IntRange(from = 0, to = 50) topLeftPercent: Int = 0,
+ @IntRange(from = 0, to = 50) topRightPercent: Int = 0,
+ @IntRange(from = 0, to = 50) bottomRightPercent: Int = 0,
+ @IntRange(from = 0, to = 50) bottomLeftPercent: Int = 0
+) = RoundedCornerShape(
+ CornerSize(topLeftPercent),
+ CornerSize(topRightPercent),
+ CornerSize(bottomRightPercent),
+ CornerSize(bottomLeftPercent)
+)
diff --git a/ui/ui-framework/api/1.0.0-alpha01.txt b/ui/ui-framework/api/1.0.0-alpha01.txt
index cdcff44..2f60003 100644
--- a/ui/ui-framework/api/1.0.0-alpha01.txt
+++ b/ui/ui-framework/api/1.0.0-alpha01.txt
@@ -8,7 +8,7 @@
public final class ComplexLayoutBlockReceiver implements androidx.ui.core.DensityReceiver {
method public androidx.ui.core.Density getDensity();
- method public androidx.ui.core.LayoutResult layoutResult(androidx.ui.core.IntPx width, androidx.ui.core.IntPx height, kotlin.jvm.functions.Function1<? super androidx.ui.core.PositioningBlockReceiver,kotlin.Unit> block);
+ method public androidx.ui.core.LayoutResult layout(androidx.ui.core.IntPx width, androidx.ui.core.IntPx height, kotlin.jvm.functions.Function1<? super androidx.ui.core.PositioningBlockReceiver,kotlin.Unit> positioningBlock);
method public androidx.ui.core.IntPx maxIntrinsicHeight(androidx.ui.core.Measurable, androidx.ui.core.IntPx w);
method public androidx.ui.core.IntPx maxIntrinsicWidth(androidx.ui.core.Measurable, androidx.ui.core.IntPx h);
method public androidx.ui.core.Placeable measure(androidx.ui.core.Measurable, androidx.ui.core.Constraints constraints);
@@ -18,9 +18,9 @@
}
public final class ComplexLayoutReceiver {
- method public void layout(kotlin.jvm.functions.Function3<? super androidx.ui.core.ComplexLayoutBlockReceiver,? super java.util.List<? extends androidx.ui.core.Measurable>,? super androidx.ui.core.Constraints,androidx.ui.core.LayoutResult> layoutBlock);
method public void maxIntrinsicHeight(kotlin.jvm.functions.Function3<? super androidx.ui.core.IntrinsicMeasurementReceiver,? super java.util.List<? extends androidx.ui.core.Measurable>,? super androidx.ui.core.IntPx,androidx.ui.core.IntPx> maxIntrinsicHeightBlock);
method public void maxIntrinsicWidth(kotlin.jvm.functions.Function3<? super androidx.ui.core.IntrinsicMeasurementReceiver,? super java.util.List<? extends androidx.ui.core.Measurable>,? super androidx.ui.core.IntPx,androidx.ui.core.IntPx> maxIntrinsicWidthBlock);
+ method public void measure(kotlin.jvm.functions.Function3<? super androidx.ui.core.ComplexLayoutBlockReceiver,? super java.util.List<? extends androidx.ui.core.Measurable>,? super androidx.ui.core.Constraints,androidx.ui.core.LayoutResult> layoutBlock);
method public void minIntrinsicHeight(kotlin.jvm.functions.Function3<? super androidx.ui.core.IntrinsicMeasurementReceiver,? super java.util.List<? extends androidx.ui.core.Measurable>,? super androidx.ui.core.IntPx,androidx.ui.core.IntPx> minIntrinsicHeightBlock);
method public void minIntrinsicWidth(kotlin.jvm.functions.Function3<? super androidx.ui.core.IntrinsicMeasurementReceiver,? super java.util.List<? extends androidx.ui.core.Measurable>,? super androidx.ui.core.IntPx,androidx.ui.core.IntPx> minIntrinsicWidthBlock);
}
@@ -36,18 +36,6 @@
method public static void DrawShadow(androidx.ui.engine.geometry.Shape shape, androidx.ui.core.Dp elevation);
}
- public final class EditorStyle {
- ctor public EditorStyle(androidx.ui.text.TextStyle? textStyle, androidx.ui.graphics.Color compositionColor, androidx.ui.graphics.Color selectionColor);
- ctor public EditorStyle();
- method public androidx.ui.text.TextStyle? component1();
- method public androidx.ui.graphics.Color component2();
- method public androidx.ui.graphics.Color component3();
- method public androidx.ui.core.EditorStyle copy(androidx.ui.text.TextStyle? textStyle, androidx.ui.graphics.Color compositionColor, androidx.ui.graphics.Color selectionColor);
- method public androidx.ui.graphics.Color getCompositionColor();
- method public androidx.ui.graphics.Color getSelectionColor();
- method public androidx.ui.text.TextStyle? getTextStyle();
- }
-
public final class IntrinsicMeasurementReceiver implements androidx.ui.core.DensityReceiver {
method public androidx.ui.core.Density getDensity();
method public androidx.ui.core.IntPx maxIntrinsicHeight(androidx.ui.core.Measurable, androidx.ui.core.IntPx w);
@@ -60,7 +48,7 @@
public final class LayoutBlockReceiver implements androidx.ui.core.DensityReceiver {
method public operator java.util.List<androidx.ui.core.Measurable> get(java.util.List<? extends androidx.ui.core.Measurable>, kotlin.jvm.functions.Function0<kotlin.Unit> children);
method public androidx.ui.core.Density getDensity();
- method public androidx.ui.core.LayoutResult layout(androidx.ui.core.IntPx width, androidx.ui.core.IntPx height, kotlin.jvm.functions.Function1<? super androidx.ui.core.PositioningBlockReceiver,kotlin.Unit> block);
+ method public androidx.ui.core.LayoutResult layout(androidx.ui.core.IntPx width, androidx.ui.core.IntPx height, kotlin.jvm.functions.Function1<? super androidx.ui.core.PositioningBlockReceiver,kotlin.Unit> positioningBlock);
method public androidx.ui.core.Placeable measure(androidx.ui.core.Measurable, androidx.ui.core.Constraints constraints);
property public androidx.ui.core.Density density;
}
@@ -80,8 +68,8 @@
public final class LayoutKt {
ctor public LayoutKt();
method public static void ComplexLayout(kotlin.jvm.functions.Function0<kotlin.Unit> children, kotlin.jvm.functions.Function1<? super androidx.ui.core.ComplexLayoutReceiver,kotlin.Unit> block);
- method public static void Layout(kotlin.jvm.functions.Function0<kotlin.Unit> children, kotlin.jvm.functions.Function3<? super androidx.ui.core.LayoutBlockReceiver,? super java.util.List<? extends androidx.ui.core.Measurable>,? super androidx.ui.core.Constraints,androidx.ui.core.LayoutResult> layoutBlock);
- method public static void Layout(kotlin.jvm.functions.Function0<kotlin.Unit>![] childrenArray, kotlin.jvm.functions.Function3<? super androidx.ui.core.LayoutBlockReceiver,? super java.util.List<? extends androidx.ui.core.Measurable>,? super androidx.ui.core.Constraints,androidx.ui.core.LayoutResult> layoutBlock);
+ method public static void Layout(kotlin.jvm.functions.Function0<kotlin.Unit> children, kotlin.jvm.functions.Function3<? super androidx.ui.core.LayoutBlockReceiver,? super java.util.List<? extends androidx.ui.core.Measurable>,? super androidx.ui.core.Constraints,androidx.ui.core.LayoutResult> measureBlock);
+ method public static void Layout(kotlin.jvm.functions.Function0<kotlin.Unit>![] childrenArray, kotlin.jvm.functions.Function3<? super androidx.ui.core.LayoutBlockReceiver,? super java.util.List<? extends androidx.ui.core.Measurable>,? super androidx.ui.core.Constraints,androidx.ui.core.LayoutResult> measureBlock);
method public static void OnChildPositioned(kotlin.jvm.functions.Function1<? super androidx.ui.core.LayoutCoordinates,kotlin.Unit> onPositioned, kotlin.jvm.functions.Function0<kotlin.Unit> children);
method public static void OnPositioned(kotlin.jvm.functions.Function1<? super androidx.ui.core.LayoutCoordinates,kotlin.Unit> onPositioned);
method public static void WithConstraints(kotlin.jvm.functions.Function1<? super androidx.ui.core.Constraints,kotlin.Unit> children);
@@ -99,11 +87,6 @@
property public abstract Object? parentData;
}
- public interface OffsetMap {
- method public int originalToTransformed(int offset);
- method public int transformedToOriginal(int offset);
- }
-
public final class OpacityKt {
ctor public OpacityKt();
method public static void Opacity(@FloatRange(from=0.0, to=1.0) float opacity, kotlin.jvm.functions.Function0<kotlin.Unit> children);
@@ -114,13 +97,6 @@
method public static void ParentData(Object data, kotlin.jvm.functions.Function0<kotlin.Unit> children);
}
- public final class PasswordVisualTransformation implements androidx.ui.core.VisualTransformation {
- ctor public PasswordVisualTransformation(char mask);
- ctor public PasswordVisualTransformation();
- method public androidx.ui.core.TransformedText filter(androidx.ui.text.AnnotatedString text);
- method public char getMask();
- }
-
public abstract class Placeable {
ctor public Placeable();
method public abstract androidx.ui.core.IntPx getHeight();
@@ -153,9 +129,13 @@
method public static androidx.compose.Ambient<java.lang.String> getTestTagAmbient();
}
+ public final class TextFieldDelegateKt {
+ ctor public TextFieldDelegateKt();
+ }
+
public final class TextFieldKt {
ctor public TextFieldKt();
- method public static void TextField(androidx.ui.input.EditorModel value, androidx.ui.core.EditorStyle editorStyle, androidx.ui.input.KeyboardType keyboardType = KeyboardType.Text, androidx.ui.input.ImeAction imeAction = ImeAction.Unspecified, kotlin.jvm.functions.Function1<? super androidx.ui.input.EditorModel,kotlin.Unit> onValueChange = {}, kotlin.jvm.functions.Function0<kotlin.Unit> onFocus = {}, kotlin.jvm.functions.Function0<kotlin.Unit> onBlur = {}, kotlin.jvm.functions.Function1<? super androidx.ui.input.ImeAction,kotlin.Unit> onImeActionPerformed = {}, androidx.ui.core.VisualTransformation? visualTransformation = null);
+ method public static void TextField(androidx.ui.input.EditorModel value, androidx.ui.input.EditorStyle editorStyle, androidx.ui.input.KeyboardType keyboardType = KeyboardType.Text, androidx.ui.input.ImeAction imeAction = ImeAction.Unspecified, kotlin.jvm.functions.Function1<? super androidx.ui.input.EditorModel,kotlin.Unit> onValueChange = {}, kotlin.jvm.functions.Function0<kotlin.Unit> onFocus = {}, kotlin.jvm.functions.Function0<kotlin.Unit> onBlur = {}, kotlin.jvm.functions.Function1<? super androidx.ui.input.ImeAction,kotlin.Unit> onImeActionPerformed = {}, androidx.ui.input.VisualTransformation? visualTransformation = null);
}
public final class TextKt {
@@ -193,23 +173,6 @@
method public androidx.ui.core.TextSpanComposition getComposer();
}
- public final class TransformedText {
- ctor public TransformedText(androidx.ui.text.AnnotatedString transformedText, androidx.ui.core.OffsetMap offsetMap);
- method public androidx.ui.text.AnnotatedString component1();
- method public androidx.ui.core.OffsetMap component2();
- method public androidx.ui.core.TransformedText copy(androidx.ui.text.AnnotatedString transformedText, androidx.ui.core.OffsetMap offsetMap);
- method public androidx.ui.core.OffsetMap getOffsetMap();
- method public androidx.ui.text.AnnotatedString getTransformedText();
- }
-
- public interface VisualTransformation {
- method public androidx.ui.core.TransformedText filter(androidx.ui.text.AnnotatedString text);
- }
-
- public final class VisualTransformationKt {
- ctor public VisualTransformationKt();
- }
-
public final class WrapperKt {
ctor public WrapperKt();
method public static void ComposeView(kotlin.jvm.functions.Function0<kotlin.Unit> children);
@@ -221,6 +184,7 @@
method public static androidx.compose.Ambient<android.content.Context> getContextAmbient();
method public static androidx.compose.Ambient<kotlin.coroutines.CoroutineContext> getCoroutineContextAmbient();
method public static androidx.compose.Ambient<androidx.ui.core.Density> getDensityAmbient();
+ method public static androidx.compose.Ambient<androidx.ui.core.LayoutDirection> getLayoutDirectionAmbient();
method public static androidx.compose.CompositionContext? setContent(android.app.Activity, kotlin.jvm.functions.Function0<kotlin.Unit> content);
method public static androidx.compose.CompositionContext? setContent(android.view.ViewGroup, kotlin.jvm.functions.Function0<kotlin.Unit> content);
method @CheckResult(suggest="+") public static <R> androidx.compose.Effect<R> withDensity(kotlin.jvm.functions.Function1<? super androidx.ui.core.DensityReceiver,? extends R> block);
diff --git a/ui/ui-framework/api/current.txt b/ui/ui-framework/api/current.txt
index cdcff44..2f60003 100644
--- a/ui/ui-framework/api/current.txt
+++ b/ui/ui-framework/api/current.txt
@@ -8,7 +8,7 @@
public final class ComplexLayoutBlockReceiver implements androidx.ui.core.DensityReceiver {
method public androidx.ui.core.Density getDensity();
- method public androidx.ui.core.LayoutResult layoutResult(androidx.ui.core.IntPx width, androidx.ui.core.IntPx height, kotlin.jvm.functions.Function1<? super androidx.ui.core.PositioningBlockReceiver,kotlin.Unit> block);
+ method public androidx.ui.core.LayoutResult layout(androidx.ui.core.IntPx width, androidx.ui.core.IntPx height, kotlin.jvm.functions.Function1<? super androidx.ui.core.PositioningBlockReceiver,kotlin.Unit> positioningBlock);
method public androidx.ui.core.IntPx maxIntrinsicHeight(androidx.ui.core.Measurable, androidx.ui.core.IntPx w);
method public androidx.ui.core.IntPx maxIntrinsicWidth(androidx.ui.core.Measurable, androidx.ui.core.IntPx h);
method public androidx.ui.core.Placeable measure(androidx.ui.core.Measurable, androidx.ui.core.Constraints constraints);
@@ -18,9 +18,9 @@
}
public final class ComplexLayoutReceiver {
- method public void layout(kotlin.jvm.functions.Function3<? super androidx.ui.core.ComplexLayoutBlockReceiver,? super java.util.List<? extends androidx.ui.core.Measurable>,? super androidx.ui.core.Constraints,androidx.ui.core.LayoutResult> layoutBlock);
method public void maxIntrinsicHeight(kotlin.jvm.functions.Function3<? super androidx.ui.core.IntrinsicMeasurementReceiver,? super java.util.List<? extends androidx.ui.core.Measurable>,? super androidx.ui.core.IntPx,androidx.ui.core.IntPx> maxIntrinsicHeightBlock);
method public void maxIntrinsicWidth(kotlin.jvm.functions.Function3<? super androidx.ui.core.IntrinsicMeasurementReceiver,? super java.util.List<? extends androidx.ui.core.Measurable>,? super androidx.ui.core.IntPx,androidx.ui.core.IntPx> maxIntrinsicWidthBlock);
+ method public void measure(kotlin.jvm.functions.Function3<? super androidx.ui.core.ComplexLayoutBlockReceiver,? super java.util.List<? extends androidx.ui.core.Measurable>,? super androidx.ui.core.Constraints,androidx.ui.core.LayoutResult> layoutBlock);
method public void minIntrinsicHeight(kotlin.jvm.functions.Function3<? super androidx.ui.core.IntrinsicMeasurementReceiver,? super java.util.List<? extends androidx.ui.core.Measurable>,? super androidx.ui.core.IntPx,androidx.ui.core.IntPx> minIntrinsicHeightBlock);
method public void minIntrinsicWidth(kotlin.jvm.functions.Function3<? super androidx.ui.core.IntrinsicMeasurementReceiver,? super java.util.List<? extends androidx.ui.core.Measurable>,? super androidx.ui.core.IntPx,androidx.ui.core.IntPx> minIntrinsicWidthBlock);
}
@@ -36,18 +36,6 @@
method public static void DrawShadow(androidx.ui.engine.geometry.Shape shape, androidx.ui.core.Dp elevation);
}
- public final class EditorStyle {
- ctor public EditorStyle(androidx.ui.text.TextStyle? textStyle, androidx.ui.graphics.Color compositionColor, androidx.ui.graphics.Color selectionColor);
- ctor public EditorStyle();
- method public androidx.ui.text.TextStyle? component1();
- method public androidx.ui.graphics.Color component2();
- method public androidx.ui.graphics.Color component3();
- method public androidx.ui.core.EditorStyle copy(androidx.ui.text.TextStyle? textStyle, androidx.ui.graphics.Color compositionColor, androidx.ui.graphics.Color selectionColor);
- method public androidx.ui.graphics.Color getCompositionColor();
- method public androidx.ui.graphics.Color getSelectionColor();
- method public androidx.ui.text.TextStyle? getTextStyle();
- }
-
public final class IntrinsicMeasurementReceiver implements androidx.ui.core.DensityReceiver {
method public androidx.ui.core.Density getDensity();
method public androidx.ui.core.IntPx maxIntrinsicHeight(androidx.ui.core.Measurable, androidx.ui.core.IntPx w);
@@ -60,7 +48,7 @@
public final class LayoutBlockReceiver implements androidx.ui.core.DensityReceiver {
method public operator java.util.List<androidx.ui.core.Measurable> get(java.util.List<? extends androidx.ui.core.Measurable>, kotlin.jvm.functions.Function0<kotlin.Unit> children);
method public androidx.ui.core.Density getDensity();
- method public androidx.ui.core.LayoutResult layout(androidx.ui.core.IntPx width, androidx.ui.core.IntPx height, kotlin.jvm.functions.Function1<? super androidx.ui.core.PositioningBlockReceiver,kotlin.Unit> block);
+ method public androidx.ui.core.LayoutResult layout(androidx.ui.core.IntPx width, androidx.ui.core.IntPx height, kotlin.jvm.functions.Function1<? super androidx.ui.core.PositioningBlockReceiver,kotlin.Unit> positioningBlock);
method public androidx.ui.core.Placeable measure(androidx.ui.core.Measurable, androidx.ui.core.Constraints constraints);
property public androidx.ui.core.Density density;
}
@@ -80,8 +68,8 @@
public final class LayoutKt {
ctor public LayoutKt();
method public static void ComplexLayout(kotlin.jvm.functions.Function0<kotlin.Unit> children, kotlin.jvm.functions.Function1<? super androidx.ui.core.ComplexLayoutReceiver,kotlin.Unit> block);
- method public static void Layout(kotlin.jvm.functions.Function0<kotlin.Unit> children, kotlin.jvm.functions.Function3<? super androidx.ui.core.LayoutBlockReceiver,? super java.util.List<? extends androidx.ui.core.Measurable>,? super androidx.ui.core.Constraints,androidx.ui.core.LayoutResult> layoutBlock);
- method public static void Layout(kotlin.jvm.functions.Function0<kotlin.Unit>![] childrenArray, kotlin.jvm.functions.Function3<? super androidx.ui.core.LayoutBlockReceiver,? super java.util.List<? extends androidx.ui.core.Measurable>,? super androidx.ui.core.Constraints,androidx.ui.core.LayoutResult> layoutBlock);
+ method public static void Layout(kotlin.jvm.functions.Function0<kotlin.Unit> children, kotlin.jvm.functions.Function3<? super androidx.ui.core.LayoutBlockReceiver,? super java.util.List<? extends androidx.ui.core.Measurable>,? super androidx.ui.core.Constraints,androidx.ui.core.LayoutResult> measureBlock);
+ method public static void Layout(kotlin.jvm.functions.Function0<kotlin.Unit>![] childrenArray, kotlin.jvm.functions.Function3<? super androidx.ui.core.LayoutBlockReceiver,? super java.util.List<? extends androidx.ui.core.Measurable>,? super androidx.ui.core.Constraints,androidx.ui.core.LayoutResult> measureBlock);
method public static void OnChildPositioned(kotlin.jvm.functions.Function1<? super androidx.ui.core.LayoutCoordinates,kotlin.Unit> onPositioned, kotlin.jvm.functions.Function0<kotlin.Unit> children);
method public static void OnPositioned(kotlin.jvm.functions.Function1<? super androidx.ui.core.LayoutCoordinates,kotlin.Unit> onPositioned);
method public static void WithConstraints(kotlin.jvm.functions.Function1<? super androidx.ui.core.Constraints,kotlin.Unit> children);
@@ -99,11 +87,6 @@
property public abstract Object? parentData;
}
- public interface OffsetMap {
- method public int originalToTransformed(int offset);
- method public int transformedToOriginal(int offset);
- }
-
public final class OpacityKt {
ctor public OpacityKt();
method public static void Opacity(@FloatRange(from=0.0, to=1.0) float opacity, kotlin.jvm.functions.Function0<kotlin.Unit> children);
@@ -114,13 +97,6 @@
method public static void ParentData(Object data, kotlin.jvm.functions.Function0<kotlin.Unit> children);
}
- public final class PasswordVisualTransformation implements androidx.ui.core.VisualTransformation {
- ctor public PasswordVisualTransformation(char mask);
- ctor public PasswordVisualTransformation();
- method public androidx.ui.core.TransformedText filter(androidx.ui.text.AnnotatedString text);
- method public char getMask();
- }
-
public abstract class Placeable {
ctor public Placeable();
method public abstract androidx.ui.core.IntPx getHeight();
@@ -153,9 +129,13 @@
method public static androidx.compose.Ambient<java.lang.String> getTestTagAmbient();
}
+ public final class TextFieldDelegateKt {
+ ctor public TextFieldDelegateKt();
+ }
+
public final class TextFieldKt {
ctor public TextFieldKt();
- method public static void TextField(androidx.ui.input.EditorModel value, androidx.ui.core.EditorStyle editorStyle, androidx.ui.input.KeyboardType keyboardType = KeyboardType.Text, androidx.ui.input.ImeAction imeAction = ImeAction.Unspecified, kotlin.jvm.functions.Function1<? super androidx.ui.input.EditorModel,kotlin.Unit> onValueChange = {}, kotlin.jvm.functions.Function0<kotlin.Unit> onFocus = {}, kotlin.jvm.functions.Function0<kotlin.Unit> onBlur = {}, kotlin.jvm.functions.Function1<? super androidx.ui.input.ImeAction,kotlin.Unit> onImeActionPerformed = {}, androidx.ui.core.VisualTransformation? visualTransformation = null);
+ method public static void TextField(androidx.ui.input.EditorModel value, androidx.ui.input.EditorStyle editorStyle, androidx.ui.input.KeyboardType keyboardType = KeyboardType.Text, androidx.ui.input.ImeAction imeAction = ImeAction.Unspecified, kotlin.jvm.functions.Function1<? super androidx.ui.input.EditorModel,kotlin.Unit> onValueChange = {}, kotlin.jvm.functions.Function0<kotlin.Unit> onFocus = {}, kotlin.jvm.functions.Function0<kotlin.Unit> onBlur = {}, kotlin.jvm.functions.Function1<? super androidx.ui.input.ImeAction,kotlin.Unit> onImeActionPerformed = {}, androidx.ui.input.VisualTransformation? visualTransformation = null);
}
public final class TextKt {
@@ -193,23 +173,6 @@
method public androidx.ui.core.TextSpanComposition getComposer();
}
- public final class TransformedText {
- ctor public TransformedText(androidx.ui.text.AnnotatedString transformedText, androidx.ui.core.OffsetMap offsetMap);
- method public androidx.ui.text.AnnotatedString component1();
- method public androidx.ui.core.OffsetMap component2();
- method public androidx.ui.core.TransformedText copy(androidx.ui.text.AnnotatedString transformedText, androidx.ui.core.OffsetMap offsetMap);
- method public androidx.ui.core.OffsetMap getOffsetMap();
- method public androidx.ui.text.AnnotatedString getTransformedText();
- }
-
- public interface VisualTransformation {
- method public androidx.ui.core.TransformedText filter(androidx.ui.text.AnnotatedString text);
- }
-
- public final class VisualTransformationKt {
- ctor public VisualTransformationKt();
- }
-
public final class WrapperKt {
ctor public WrapperKt();
method public static void ComposeView(kotlin.jvm.functions.Function0<kotlin.Unit> children);
@@ -221,6 +184,7 @@
method public static androidx.compose.Ambient<android.content.Context> getContextAmbient();
method public static androidx.compose.Ambient<kotlin.coroutines.CoroutineContext> getCoroutineContextAmbient();
method public static androidx.compose.Ambient<androidx.ui.core.Density> getDensityAmbient();
+ method public static androidx.compose.Ambient<androidx.ui.core.LayoutDirection> getLayoutDirectionAmbient();
method public static androidx.compose.CompositionContext? setContent(android.app.Activity, kotlin.jvm.functions.Function0<kotlin.Unit> content);
method public static androidx.compose.CompositionContext? setContent(android.view.ViewGroup, kotlin.jvm.functions.Function0<kotlin.Unit> content);
method @CheckResult(suggest="+") public static <R> androidx.compose.Effect<R> withDensity(kotlin.jvm.functions.Function1<? super androidx.ui.core.DensityReceiver,? extends R> block);
diff --git a/ui/ui-framework/api/restricted_1.0.0-alpha01.txt b/ui/ui-framework/api/restricted_1.0.0-alpha01.txt
index cdcff44..2f60003 100644
--- a/ui/ui-framework/api/restricted_1.0.0-alpha01.txt
+++ b/ui/ui-framework/api/restricted_1.0.0-alpha01.txt
@@ -8,7 +8,7 @@
public final class ComplexLayoutBlockReceiver implements androidx.ui.core.DensityReceiver {
method public androidx.ui.core.Density getDensity();
- method public androidx.ui.core.LayoutResult layoutResult(androidx.ui.core.IntPx width, androidx.ui.core.IntPx height, kotlin.jvm.functions.Function1<? super androidx.ui.core.PositioningBlockReceiver,kotlin.Unit> block);
+ method public androidx.ui.core.LayoutResult layout(androidx.ui.core.IntPx width, androidx.ui.core.IntPx height, kotlin.jvm.functions.Function1<? super androidx.ui.core.PositioningBlockReceiver,kotlin.Unit> positioningBlock);
method public androidx.ui.core.IntPx maxIntrinsicHeight(androidx.ui.core.Measurable, androidx.ui.core.IntPx w);
method public androidx.ui.core.IntPx maxIntrinsicWidth(androidx.ui.core.Measurable, androidx.ui.core.IntPx h);
method public androidx.ui.core.Placeable measure(androidx.ui.core.Measurable, androidx.ui.core.Constraints constraints);
@@ -18,9 +18,9 @@
}
public final class ComplexLayoutReceiver {
- method public void layout(kotlin.jvm.functions.Function3<? super androidx.ui.core.ComplexLayoutBlockReceiver,? super java.util.List<? extends androidx.ui.core.Measurable>,? super androidx.ui.core.Constraints,androidx.ui.core.LayoutResult> layoutBlock);
method public void maxIntrinsicHeight(kotlin.jvm.functions.Function3<? super androidx.ui.core.IntrinsicMeasurementReceiver,? super java.util.List<? extends androidx.ui.core.Measurable>,? super androidx.ui.core.IntPx,androidx.ui.core.IntPx> maxIntrinsicHeightBlock);
method public void maxIntrinsicWidth(kotlin.jvm.functions.Function3<? super androidx.ui.core.IntrinsicMeasurementReceiver,? super java.util.List<? extends androidx.ui.core.Measurable>,? super androidx.ui.core.IntPx,androidx.ui.core.IntPx> maxIntrinsicWidthBlock);
+ method public void measure(kotlin.jvm.functions.Function3<? super androidx.ui.core.ComplexLayoutBlockReceiver,? super java.util.List<? extends androidx.ui.core.Measurable>,? super androidx.ui.core.Constraints,androidx.ui.core.LayoutResult> layoutBlock);
method public void minIntrinsicHeight(kotlin.jvm.functions.Function3<? super androidx.ui.core.IntrinsicMeasurementReceiver,? super java.util.List<? extends androidx.ui.core.Measurable>,? super androidx.ui.core.IntPx,androidx.ui.core.IntPx> minIntrinsicHeightBlock);
method public void minIntrinsicWidth(kotlin.jvm.functions.Function3<? super androidx.ui.core.IntrinsicMeasurementReceiver,? super java.util.List<? extends androidx.ui.core.Measurable>,? super androidx.ui.core.IntPx,androidx.ui.core.IntPx> minIntrinsicWidthBlock);
}
@@ -36,18 +36,6 @@
method public static void DrawShadow(androidx.ui.engine.geometry.Shape shape, androidx.ui.core.Dp elevation);
}
- public final class EditorStyle {
- ctor public EditorStyle(androidx.ui.text.TextStyle? textStyle, androidx.ui.graphics.Color compositionColor, androidx.ui.graphics.Color selectionColor);
- ctor public EditorStyle();
- method public androidx.ui.text.TextStyle? component1();
- method public androidx.ui.graphics.Color component2();
- method public androidx.ui.graphics.Color component3();
- method public androidx.ui.core.EditorStyle copy(androidx.ui.text.TextStyle? textStyle, androidx.ui.graphics.Color compositionColor, androidx.ui.graphics.Color selectionColor);
- method public androidx.ui.graphics.Color getCompositionColor();
- method public androidx.ui.graphics.Color getSelectionColor();
- method public androidx.ui.text.TextStyle? getTextStyle();
- }
-
public final class IntrinsicMeasurementReceiver implements androidx.ui.core.DensityReceiver {
method public androidx.ui.core.Density getDensity();
method public androidx.ui.core.IntPx maxIntrinsicHeight(androidx.ui.core.Measurable, androidx.ui.core.IntPx w);
@@ -60,7 +48,7 @@
public final class LayoutBlockReceiver implements androidx.ui.core.DensityReceiver {
method public operator java.util.List<androidx.ui.core.Measurable> get(java.util.List<? extends androidx.ui.core.Measurable>, kotlin.jvm.functions.Function0<kotlin.Unit> children);
method public androidx.ui.core.Density getDensity();
- method public androidx.ui.core.LayoutResult layout(androidx.ui.core.IntPx width, androidx.ui.core.IntPx height, kotlin.jvm.functions.Function1<? super androidx.ui.core.PositioningBlockReceiver,kotlin.Unit> block);
+ method public androidx.ui.core.LayoutResult layout(androidx.ui.core.IntPx width, androidx.ui.core.IntPx height, kotlin.jvm.functions.Function1<? super androidx.ui.core.PositioningBlockReceiver,kotlin.Unit> positioningBlock);
method public androidx.ui.core.Placeable measure(androidx.ui.core.Measurable, androidx.ui.core.Constraints constraints);
property public androidx.ui.core.Density density;
}
@@ -80,8 +68,8 @@
public final class LayoutKt {
ctor public LayoutKt();
method public static void ComplexLayout(kotlin.jvm.functions.Function0<kotlin.Unit> children, kotlin.jvm.functions.Function1<? super androidx.ui.core.ComplexLayoutReceiver,kotlin.Unit> block);
- method public static void Layout(kotlin.jvm.functions.Function0<kotlin.Unit> children, kotlin.jvm.functions.Function3<? super androidx.ui.core.LayoutBlockReceiver,? super java.util.List<? extends androidx.ui.core.Measurable>,? super androidx.ui.core.Constraints,androidx.ui.core.LayoutResult> layoutBlock);
- method public static void Layout(kotlin.jvm.functions.Function0<kotlin.Unit>![] childrenArray, kotlin.jvm.functions.Function3<? super androidx.ui.core.LayoutBlockReceiver,? super java.util.List<? extends androidx.ui.core.Measurable>,? super androidx.ui.core.Constraints,androidx.ui.core.LayoutResult> layoutBlock);
+ method public static void Layout(kotlin.jvm.functions.Function0<kotlin.Unit> children, kotlin.jvm.functions.Function3<? super androidx.ui.core.LayoutBlockReceiver,? super java.util.List<? extends androidx.ui.core.Measurable>,? super androidx.ui.core.Constraints,androidx.ui.core.LayoutResult> measureBlock);
+ method public static void Layout(kotlin.jvm.functions.Function0<kotlin.Unit>![] childrenArray, kotlin.jvm.functions.Function3<? super androidx.ui.core.LayoutBlockReceiver,? super java.util.List<? extends androidx.ui.core.Measurable>,? super androidx.ui.core.Constraints,androidx.ui.core.LayoutResult> measureBlock);
method public static void OnChildPositioned(kotlin.jvm.functions.Function1<? super androidx.ui.core.LayoutCoordinates,kotlin.Unit> onPositioned, kotlin.jvm.functions.Function0<kotlin.Unit> children);
method public static void OnPositioned(kotlin.jvm.functions.Function1<? super androidx.ui.core.LayoutCoordinates,kotlin.Unit> onPositioned);
method public static void WithConstraints(kotlin.jvm.functions.Function1<? super androidx.ui.core.Constraints,kotlin.Unit> children);
@@ -99,11 +87,6 @@
property public abstract Object? parentData;
}
- public interface OffsetMap {
- method public int originalToTransformed(int offset);
- method public int transformedToOriginal(int offset);
- }
-
public final class OpacityKt {
ctor public OpacityKt();
method public static void Opacity(@FloatRange(from=0.0, to=1.0) float opacity, kotlin.jvm.functions.Function0<kotlin.Unit> children);
@@ -114,13 +97,6 @@
method public static void ParentData(Object data, kotlin.jvm.functions.Function0<kotlin.Unit> children);
}
- public final class PasswordVisualTransformation implements androidx.ui.core.VisualTransformation {
- ctor public PasswordVisualTransformation(char mask);
- ctor public PasswordVisualTransformation();
- method public androidx.ui.core.TransformedText filter(androidx.ui.text.AnnotatedString text);
- method public char getMask();
- }
-
public abstract class Placeable {
ctor public Placeable();
method public abstract androidx.ui.core.IntPx getHeight();
@@ -153,9 +129,13 @@
method public static androidx.compose.Ambient<java.lang.String> getTestTagAmbient();
}
+ public final class TextFieldDelegateKt {
+ ctor public TextFieldDelegateKt();
+ }
+
public final class TextFieldKt {
ctor public TextFieldKt();
- method public static void TextField(androidx.ui.input.EditorModel value, androidx.ui.core.EditorStyle editorStyle, androidx.ui.input.KeyboardType keyboardType = KeyboardType.Text, androidx.ui.input.ImeAction imeAction = ImeAction.Unspecified, kotlin.jvm.functions.Function1<? super androidx.ui.input.EditorModel,kotlin.Unit> onValueChange = {}, kotlin.jvm.functions.Function0<kotlin.Unit> onFocus = {}, kotlin.jvm.functions.Function0<kotlin.Unit> onBlur = {}, kotlin.jvm.functions.Function1<? super androidx.ui.input.ImeAction,kotlin.Unit> onImeActionPerformed = {}, androidx.ui.core.VisualTransformation? visualTransformation = null);
+ method public static void TextField(androidx.ui.input.EditorModel value, androidx.ui.input.EditorStyle editorStyle, androidx.ui.input.KeyboardType keyboardType = KeyboardType.Text, androidx.ui.input.ImeAction imeAction = ImeAction.Unspecified, kotlin.jvm.functions.Function1<? super androidx.ui.input.EditorModel,kotlin.Unit> onValueChange = {}, kotlin.jvm.functions.Function0<kotlin.Unit> onFocus = {}, kotlin.jvm.functions.Function0<kotlin.Unit> onBlur = {}, kotlin.jvm.functions.Function1<? super androidx.ui.input.ImeAction,kotlin.Unit> onImeActionPerformed = {}, androidx.ui.input.VisualTransformation? visualTransformation = null);
}
public final class TextKt {
@@ -193,23 +173,6 @@
method public androidx.ui.core.TextSpanComposition getComposer();
}
- public final class TransformedText {
- ctor public TransformedText(androidx.ui.text.AnnotatedString transformedText, androidx.ui.core.OffsetMap offsetMap);
- method public androidx.ui.text.AnnotatedString component1();
- method public androidx.ui.core.OffsetMap component2();
- method public androidx.ui.core.TransformedText copy(androidx.ui.text.AnnotatedString transformedText, androidx.ui.core.OffsetMap offsetMap);
- method public androidx.ui.core.OffsetMap getOffsetMap();
- method public androidx.ui.text.AnnotatedString getTransformedText();
- }
-
- public interface VisualTransformation {
- method public androidx.ui.core.TransformedText filter(androidx.ui.text.AnnotatedString text);
- }
-
- public final class VisualTransformationKt {
- ctor public VisualTransformationKt();
- }
-
public final class WrapperKt {
ctor public WrapperKt();
method public static void ComposeView(kotlin.jvm.functions.Function0<kotlin.Unit> children);
@@ -221,6 +184,7 @@
method public static androidx.compose.Ambient<android.content.Context> getContextAmbient();
method public static androidx.compose.Ambient<kotlin.coroutines.CoroutineContext> getCoroutineContextAmbient();
method public static androidx.compose.Ambient<androidx.ui.core.Density> getDensityAmbient();
+ method public static androidx.compose.Ambient<androidx.ui.core.LayoutDirection> getLayoutDirectionAmbient();
method public static androidx.compose.CompositionContext? setContent(android.app.Activity, kotlin.jvm.functions.Function0<kotlin.Unit> content);
method public static androidx.compose.CompositionContext? setContent(android.view.ViewGroup, kotlin.jvm.functions.Function0<kotlin.Unit> content);
method @CheckResult(suggest="+") public static <R> androidx.compose.Effect<R> withDensity(kotlin.jvm.functions.Function1<? super androidx.ui.core.DensityReceiver,? extends R> block);
diff --git a/ui/ui-framework/api/restricted_current.txt b/ui/ui-framework/api/restricted_current.txt
index cdcff44..2f60003 100644
--- a/ui/ui-framework/api/restricted_current.txt
+++ b/ui/ui-framework/api/restricted_current.txt
@@ -8,7 +8,7 @@
public final class ComplexLayoutBlockReceiver implements androidx.ui.core.DensityReceiver {
method public androidx.ui.core.Density getDensity();
- method public androidx.ui.core.LayoutResult layoutResult(androidx.ui.core.IntPx width, androidx.ui.core.IntPx height, kotlin.jvm.functions.Function1<? super androidx.ui.core.PositioningBlockReceiver,kotlin.Unit> block);
+ method public androidx.ui.core.LayoutResult layout(androidx.ui.core.IntPx width, androidx.ui.core.IntPx height, kotlin.jvm.functions.Function1<? super androidx.ui.core.PositioningBlockReceiver,kotlin.Unit> positioningBlock);
method public androidx.ui.core.IntPx maxIntrinsicHeight(androidx.ui.core.Measurable, androidx.ui.core.IntPx w);
method public androidx.ui.core.IntPx maxIntrinsicWidth(androidx.ui.core.Measurable, androidx.ui.core.IntPx h);
method public androidx.ui.core.Placeable measure(androidx.ui.core.Measurable, androidx.ui.core.Constraints constraints);
@@ -18,9 +18,9 @@
}
public final class ComplexLayoutReceiver {
- method public void layout(kotlin.jvm.functions.Function3<? super androidx.ui.core.ComplexLayoutBlockReceiver,? super java.util.List<? extends androidx.ui.core.Measurable>,? super androidx.ui.core.Constraints,androidx.ui.core.LayoutResult> layoutBlock);
method public void maxIntrinsicHeight(kotlin.jvm.functions.Function3<? super androidx.ui.core.IntrinsicMeasurementReceiver,? super java.util.List<? extends androidx.ui.core.Measurable>,? super androidx.ui.core.IntPx,androidx.ui.core.IntPx> maxIntrinsicHeightBlock);
method public void maxIntrinsicWidth(kotlin.jvm.functions.Function3<? super androidx.ui.core.IntrinsicMeasurementReceiver,? super java.util.List<? extends androidx.ui.core.Measurable>,? super androidx.ui.core.IntPx,androidx.ui.core.IntPx> maxIntrinsicWidthBlock);
+ method public void measure(kotlin.jvm.functions.Function3<? super androidx.ui.core.ComplexLayoutBlockReceiver,? super java.util.List<? extends androidx.ui.core.Measurable>,? super androidx.ui.core.Constraints,androidx.ui.core.LayoutResult> layoutBlock);
method public void minIntrinsicHeight(kotlin.jvm.functions.Function3<? super androidx.ui.core.IntrinsicMeasurementReceiver,? super java.util.List<? extends androidx.ui.core.Measurable>,? super androidx.ui.core.IntPx,androidx.ui.core.IntPx> minIntrinsicHeightBlock);
method public void minIntrinsicWidth(kotlin.jvm.functions.Function3<? super androidx.ui.core.IntrinsicMeasurementReceiver,? super java.util.List<? extends androidx.ui.core.Measurable>,? super androidx.ui.core.IntPx,androidx.ui.core.IntPx> minIntrinsicWidthBlock);
}
@@ -36,18 +36,6 @@
method public static void DrawShadow(androidx.ui.engine.geometry.Shape shape, androidx.ui.core.Dp elevation);
}
- public final class EditorStyle {
- ctor public EditorStyle(androidx.ui.text.TextStyle? textStyle, androidx.ui.graphics.Color compositionColor, androidx.ui.graphics.Color selectionColor);
- ctor public EditorStyle();
- method public androidx.ui.text.TextStyle? component1();
- method public androidx.ui.graphics.Color component2();
- method public androidx.ui.graphics.Color component3();
- method public androidx.ui.core.EditorStyle copy(androidx.ui.text.TextStyle? textStyle, androidx.ui.graphics.Color compositionColor, androidx.ui.graphics.Color selectionColor);
- method public androidx.ui.graphics.Color getCompositionColor();
- method public androidx.ui.graphics.Color getSelectionColor();
- method public androidx.ui.text.TextStyle? getTextStyle();
- }
-
public final class IntrinsicMeasurementReceiver implements androidx.ui.core.DensityReceiver {
method public androidx.ui.core.Density getDensity();
method public androidx.ui.core.IntPx maxIntrinsicHeight(androidx.ui.core.Measurable, androidx.ui.core.IntPx w);
@@ -60,7 +48,7 @@
public final class LayoutBlockReceiver implements androidx.ui.core.DensityReceiver {
method public operator java.util.List<androidx.ui.core.Measurable> get(java.util.List<? extends androidx.ui.core.Measurable>, kotlin.jvm.functions.Function0<kotlin.Unit> children);
method public androidx.ui.core.Density getDensity();
- method public androidx.ui.core.LayoutResult layout(androidx.ui.core.IntPx width, androidx.ui.core.IntPx height, kotlin.jvm.functions.Function1<? super androidx.ui.core.PositioningBlockReceiver,kotlin.Unit> block);
+ method public androidx.ui.core.LayoutResult layout(androidx.ui.core.IntPx width, androidx.ui.core.IntPx height, kotlin.jvm.functions.Function1<? super androidx.ui.core.PositioningBlockReceiver,kotlin.Unit> positioningBlock);
method public androidx.ui.core.Placeable measure(androidx.ui.core.Measurable, androidx.ui.core.Constraints constraints);
property public androidx.ui.core.Density density;
}
@@ -80,8 +68,8 @@
public final class LayoutKt {
ctor public LayoutKt();
method public static void ComplexLayout(kotlin.jvm.functions.Function0<kotlin.Unit> children, kotlin.jvm.functions.Function1<? super androidx.ui.core.ComplexLayoutReceiver,kotlin.Unit> block);
- method public static void Layout(kotlin.jvm.functions.Function0<kotlin.Unit> children, kotlin.jvm.functions.Function3<? super androidx.ui.core.LayoutBlockReceiver,? super java.util.List<? extends androidx.ui.core.Measurable>,? super androidx.ui.core.Constraints,androidx.ui.core.LayoutResult> layoutBlock);
- method public static void Layout(kotlin.jvm.functions.Function0<kotlin.Unit>![] childrenArray, kotlin.jvm.functions.Function3<? super androidx.ui.core.LayoutBlockReceiver,? super java.util.List<? extends androidx.ui.core.Measurable>,? super androidx.ui.core.Constraints,androidx.ui.core.LayoutResult> layoutBlock);
+ method public static void Layout(kotlin.jvm.functions.Function0<kotlin.Unit> children, kotlin.jvm.functions.Function3<? super androidx.ui.core.LayoutBlockReceiver,? super java.util.List<? extends androidx.ui.core.Measurable>,? super androidx.ui.core.Constraints,androidx.ui.core.LayoutResult> measureBlock);
+ method public static void Layout(kotlin.jvm.functions.Function0<kotlin.Unit>![] childrenArray, kotlin.jvm.functions.Function3<? super androidx.ui.core.LayoutBlockReceiver,? super java.util.List<? extends androidx.ui.core.Measurable>,? super androidx.ui.core.Constraints,androidx.ui.core.LayoutResult> measureBlock);
method public static void OnChildPositioned(kotlin.jvm.functions.Function1<? super androidx.ui.core.LayoutCoordinates,kotlin.Unit> onPositioned, kotlin.jvm.functions.Function0<kotlin.Unit> children);
method public static void OnPositioned(kotlin.jvm.functions.Function1<? super androidx.ui.core.LayoutCoordinates,kotlin.Unit> onPositioned);
method public static void WithConstraints(kotlin.jvm.functions.Function1<? super androidx.ui.core.Constraints,kotlin.Unit> children);
@@ -99,11 +87,6 @@
property public abstract Object? parentData;
}
- public interface OffsetMap {
- method public int originalToTransformed(int offset);
- method public int transformedToOriginal(int offset);
- }
-
public final class OpacityKt {
ctor public OpacityKt();
method public static void Opacity(@FloatRange(from=0.0, to=1.0) float opacity, kotlin.jvm.functions.Function0<kotlin.Unit> children);
@@ -114,13 +97,6 @@
method public static void ParentData(Object data, kotlin.jvm.functions.Function0<kotlin.Unit> children);
}
- public final class PasswordVisualTransformation implements androidx.ui.core.VisualTransformation {
- ctor public PasswordVisualTransformation(char mask);
- ctor public PasswordVisualTransformation();
- method public androidx.ui.core.TransformedText filter(androidx.ui.text.AnnotatedString text);
- method public char getMask();
- }
-
public abstract class Placeable {
ctor public Placeable();
method public abstract androidx.ui.core.IntPx getHeight();
@@ -153,9 +129,13 @@
method public static androidx.compose.Ambient<java.lang.String> getTestTagAmbient();
}
+ public final class TextFieldDelegateKt {
+ ctor public TextFieldDelegateKt();
+ }
+
public final class TextFieldKt {
ctor public TextFieldKt();
- method public static void TextField(androidx.ui.input.EditorModel value, androidx.ui.core.EditorStyle editorStyle, androidx.ui.input.KeyboardType keyboardType = KeyboardType.Text, androidx.ui.input.ImeAction imeAction = ImeAction.Unspecified, kotlin.jvm.functions.Function1<? super androidx.ui.input.EditorModel,kotlin.Unit> onValueChange = {}, kotlin.jvm.functions.Function0<kotlin.Unit> onFocus = {}, kotlin.jvm.functions.Function0<kotlin.Unit> onBlur = {}, kotlin.jvm.functions.Function1<? super androidx.ui.input.ImeAction,kotlin.Unit> onImeActionPerformed = {}, androidx.ui.core.VisualTransformation? visualTransformation = null);
+ method public static void TextField(androidx.ui.input.EditorModel value, androidx.ui.input.EditorStyle editorStyle, androidx.ui.input.KeyboardType keyboardType = KeyboardType.Text, androidx.ui.input.ImeAction imeAction = ImeAction.Unspecified, kotlin.jvm.functions.Function1<? super androidx.ui.input.EditorModel,kotlin.Unit> onValueChange = {}, kotlin.jvm.functions.Function0<kotlin.Unit> onFocus = {}, kotlin.jvm.functions.Function0<kotlin.Unit> onBlur = {}, kotlin.jvm.functions.Function1<? super androidx.ui.input.ImeAction,kotlin.Unit> onImeActionPerformed = {}, androidx.ui.input.VisualTransformation? visualTransformation = null);
}
public final class TextKt {
@@ -193,23 +173,6 @@
method public androidx.ui.core.TextSpanComposition getComposer();
}
- public final class TransformedText {
- ctor public TransformedText(androidx.ui.text.AnnotatedString transformedText, androidx.ui.core.OffsetMap offsetMap);
- method public androidx.ui.text.AnnotatedString component1();
- method public androidx.ui.core.OffsetMap component2();
- method public androidx.ui.core.TransformedText copy(androidx.ui.text.AnnotatedString transformedText, androidx.ui.core.OffsetMap offsetMap);
- method public androidx.ui.core.OffsetMap getOffsetMap();
- method public androidx.ui.text.AnnotatedString getTransformedText();
- }
-
- public interface VisualTransformation {
- method public androidx.ui.core.TransformedText filter(androidx.ui.text.AnnotatedString text);
- }
-
- public final class VisualTransformationKt {
- ctor public VisualTransformationKt();
- }
-
public final class WrapperKt {
ctor public WrapperKt();
method public static void ComposeView(kotlin.jvm.functions.Function0<kotlin.Unit> children);
@@ -221,6 +184,7 @@
method public static androidx.compose.Ambient<android.content.Context> getContextAmbient();
method public static androidx.compose.Ambient<kotlin.coroutines.CoroutineContext> getCoroutineContextAmbient();
method public static androidx.compose.Ambient<androidx.ui.core.Density> getDensityAmbient();
+ method public static androidx.compose.Ambient<androidx.ui.core.LayoutDirection> getLayoutDirectionAmbient();
method public static androidx.compose.CompositionContext? setContent(android.app.Activity, kotlin.jvm.functions.Function0<kotlin.Unit> content);
method public static androidx.compose.CompositionContext? setContent(android.view.ViewGroup, kotlin.jvm.functions.Function0<kotlin.Unit> content);
method @CheckResult(suggest="+") public static <R> androidx.compose.Effect<R> withDensity(kotlin.jvm.functions.Function1<? super androidx.ui.core.DensityReceiver,? extends R> block);
diff --git a/ui/ui-framework/build.gradle b/ui/ui-framework/build.gradle
index c0a57c1..c6d3a99 100644
--- a/ui/ui-framework/build.gradle
+++ b/ui/ui-framework/build.gradle
@@ -51,9 +51,16 @@
exclude group: 'org.mockito' // to keep control on the mockito version
}
+ androidTestImplementation project(":ui:ui-test")
+
androidTestImplementation(ANDROIDX_TEST_RULES)
androidTestImplementation(ANDROIDX_TEST_RUNNER)
androidTestImplementation(JUNIT)
+ androidTestImplementation(DEXMAKER_MOCKITO, libs.exclude_bytebuddy) // DexMaker has it"s own MockMaker
+ androidTestImplementation(MOCKITO_CORE, libs.exclude_bytebuddy) // DexMaker has it"s own MockMaker
+ androidTestImplementation MOCKITO_KOTLIN, {
+ exclude group: 'org.mockito' // to keep control on the mockito version
+ }
}
androidx {
diff --git a/ui/ui-framework/integration-tests/framework-demos/src/main/java/androidx/ui/framework/demos/AnimationGestureSemanticsActivity.kt b/ui/ui-framework/integration-tests/framework-demos/src/main/java/androidx/ui/framework/demos/AnimationGestureSemanticsActivity.kt
index 9646e80..5fde1f2 100644
--- a/ui/ui-framework/integration-tests/framework-demos/src/main/java/androidx/ui/framework/demos/AnimationGestureSemanticsActivity.kt
+++ b/ui/ui-framework/integration-tests/framework-demos/src/main/java/androidx/ui/framework/demos/AnimationGestureSemanticsActivity.kt
@@ -205,7 +205,7 @@
) { state ->
Circle(color = state[colorKey], sizeRatio = state[sizeKey])
}
- }, layoutBlock = { _, constraints ->
+ }, measureBlock = { _, constraints ->
layout(constraints.maxWidth, constraints.maxHeight) {}
})
}
diff --git a/ui/ui-framework/integration-tests/framework-demos/src/main/java/androidx/ui/framework/demos/MultipleCollect.kt b/ui/ui-framework/integration-tests/framework-demos/src/main/java/androidx/ui/framework/demos/MultipleCollect.kt
index e5fc52d..f1b1249 100644
--- a/ui/ui-framework/integration-tests/framework-demos/src/main/java/androidx/ui/framework/demos/MultipleCollect.kt
+++ b/ui/ui-framework/integration-tests/framework-demos/src/main/java/androidx/ui/framework/demos/MultipleCollect.kt
@@ -27,7 +27,6 @@
import androidx.ui.core.toRect
import androidx.ui.graphics.Color
import androidx.ui.painting.Paint
-import androidx.compose.Children
import androidx.compose.Composable
import androidx.compose.composer
import androidx.ui.graphics.Brush
@@ -35,14 +34,14 @@
@Composable
fun ColoredRect(brush: Brush, width: Dp? = null, height: Dp? = null) {
- Layout(children = { DrawFillRect(brush = brush) }, layoutBlock = { _, constraints ->
+ Layout(children = { DrawFillRect(brush = brush) }) { _, constraints ->
layout(
width?.toIntPx()?.coerceIn(constraints.minWidth, constraints.maxWidth)
?: constraints.maxWidth,
height?.toIntPx()?.coerceIn(constraints.minHeight, constraints.maxHeight)
?: constraints.maxHeight
) {}
- })
+ }
}
@Composable
@@ -66,33 +65,31 @@
content: @Composable() () -> Unit
) {
@Suppress("USELESS_CAST")
- Layout(
- childrenArray = arrayOf(header, content, footer),
- layoutBlock = { measurables, constraints ->
- val headerPlaceable = measurables[header].first().measure(
- Constraints.tightConstraints(constraints.maxWidth, 100.ipx)
- )
- val footerPadding = 50.ipx
- val footerPlaceable = measurables[footer].first().measure(
- Constraints.tightConstraints(constraints.maxWidth - footerPadding * 2, 100.ipx)
- )
- val itemHeight =
- (constraints.maxHeight - headerPlaceable.height - footerPlaceable.height) /
- measurables[content].size
- val contentPlaceables = measurables[content].map { measurable ->
- measurable.measure(Constraints.tightConstraints(constraints.maxWidth, itemHeight))
- }
+ Layout(header, content, footer) { measurables, constraints ->
+ val headerPlaceable = measurables[header].first().measure(
+ Constraints.tightConstraints(constraints.maxWidth, 100.ipx)
+ )
+ val footerPadding = 50.ipx
+ val footerPlaceable = measurables[footer].first().measure(
+ Constraints.tightConstraints(constraints.maxWidth - footerPadding * 2, 100.ipx)
+ )
+ val itemHeight =
+ (constraints.maxHeight - headerPlaceable.height - footerPlaceable.height) /
+ measurables[content].size
+ val contentPlaceables = measurables[content].map { measurable ->
+ measurable.measure(Constraints.tightConstraints(constraints.maxWidth, itemHeight))
+ }
- layout(constraints.maxWidth, constraints.maxHeight) {
- headerPlaceable.place(0.ipx, 0.ipx)
- footerPlaceable.place(footerPadding, constraints.maxHeight - footerPlaceable.height)
- var top = headerPlaceable.height
- contentPlaceables.forEach { placeable ->
- placeable.place(0.ipx, top)
- top += itemHeight
- }
+ layout(constraints.maxWidth, constraints.maxHeight) {
+ headerPlaceable.place(0.ipx, 0.ipx)
+ footerPlaceable.place(footerPadding, constraints.maxHeight - footerPlaceable.height)
+ var top = headerPlaceable.height
+ contentPlaceables.forEach { placeable ->
+ placeable.place(0.ipx, top)
+ top += itemHeight
}
- })
+ }
+ }
}
@Composable
diff --git a/ui/ui-framework/integration-tests/framework-demos/src/main/java/androidx/ui/framework/demos/autofill/ExplicitAutofillTypesActivity.kt b/ui/ui-framework/integration-tests/framework-demos/src/main/java/androidx/ui/framework/demos/autofill/ExplicitAutofillTypesActivity.kt
index 3716bfe..dca00a9 100644
--- a/ui/ui-framework/integration-tests/framework-demos/src/main/java/androidx/ui/framework/demos/autofill/ExplicitAutofillTypesActivity.kt
+++ b/ui/ui-framework/integration-tests/framework-demos/src/main/java/androidx/ui/framework/demos/autofill/ExplicitAutofillTypesActivity.kt
@@ -29,7 +29,6 @@
import androidx.ui.autofill.AutofillType
import androidx.ui.core.AutofillAmbient
import androidx.ui.core.AutofillTreeAmbient
-import androidx.ui.core.EditorStyle
import androidx.ui.core.TextField
import androidx.ui.core.Text
import androidx.ui.material.themeTextStyle
@@ -39,6 +38,7 @@
import androidx.ui.core.dp
import androidx.ui.core.setContent
import androidx.ui.input.EditorModel
+import androidx.ui.input.EditorStyle
import androidx.ui.input.ImeAction
import androidx.ui.input.KeyboardType
import androidx.ui.layout.Column
diff --git a/ui/ui-framework/integration-tests/framework-demos/src/main/java/androidx/ui/framework/demos/gestures/NestedScrollingDemo.kt b/ui/ui-framework/integration-tests/framework-demos/src/main/java/androidx/ui/framework/demos/gestures/NestedScrollingDemo.kt
index 6146504..93e3407 100644
--- a/ui/ui-framework/integration-tests/framework-demos/src/main/java/androidx/ui/framework/demos/gestures/NestedScrollingDemo.kt
+++ b/ui/ui-framework/integration-tests/framework-demos/src/main/java/androidx/ui/framework/demos/gestures/NestedScrollingDemo.kt
@@ -118,7 +118,7 @@
Draw { canvas, _ ->
canvas.restore()
}
- }, layoutBlock = { measurables, constraints ->
+ }, measureBlock = { measurables, constraints ->
val placeable =
measurables.first()
.measure(constraints.copy(minHeight = 0.ipx, maxHeight = IntPx.Infinity))
diff --git a/ui/ui-framework/integration-tests/framework-demos/src/main/java/androidx/ui/framework/demos/gestures/SimpleComposables.kt b/ui/ui-framework/integration-tests/framework-demos/src/main/java/androidx/ui/framework/demos/gestures/SimpleComposables.kt
index f1bfbd2..4cc8df7 100644
--- a/ui/ui-framework/integration-tests/framework-demos/src/main/java/androidx/ui/framework/demos/gestures/SimpleComposables.kt
+++ b/ui/ui-framework/integration-tests/framework-demos/src/main/java/androidx/ui/framework/demos/gestures/SimpleComposables.kt
@@ -129,7 +129,7 @@
)
}
},
- layoutBlock = { measurables, constraints ->
+ measureBlock = { measurables, constraints ->
val placeable =
if (measurables.isNotEmpty()) measurables.first().measure(constraints) else null
val layoutWidth = placeable?.width ?: constraints.maxWidth
diff --git a/ui/ui-framework/src/androidTest/AndroidManifest.xml b/ui/ui-framework/src/androidTest/AndroidManifest.xml
index 3827335..2f9d52f 100644
--- a/ui/ui-framework/src/androidTest/AndroidManifest.xml
+++ b/ui/ui-framework/src/androidTest/AndroidManifest.xml
@@ -21,5 +21,7 @@
<activity
android:name="androidx.ui.framework.test.TestActivity"
android:theme="@android:style/Theme.Material.NoActionBar.Fullscreen" />
+ <activity
+ android:name="androidx.ui.test.android.DefaultTestActivity" />
</application>
</manifest>
diff --git a/ui/ui-framework/src/androidTest/java/androidx/ui/core/test/AndroidLayoutDrawTest.kt b/ui/ui-framework/src/androidTest/java/androidx/ui/core/test/AndroidLayoutDrawTest.kt
index c8866dc..48461f4 100644
--- a/ui/ui-framework/src/androidTest/java/androidx/ui/core/test/AndroidLayoutDrawTest.kt
+++ b/ui/ui-framework/src/androidTest/java/androidx/ui/core/test/AndroidLayoutDrawTest.kt
@@ -24,7 +24,6 @@
import android.view.ViewGroup
import android.view.ViewTreeObserver
import androidx.annotation.RequiresApi
-import androidx.compose.Children
import androidx.compose.Composable
import androidx.compose.Compose
import androidx.compose.Model
@@ -289,7 +288,7 @@
Padding(size = model.size) {
FillColor(model.innerColor)
}
- }, layoutBlock = { measurables, constraints ->
+ }, measureBlock = { measurables, constraints ->
val placeables = measurables.map { it.measure(constraints) }
layout(placeables[0].width, placeables[0].height) {
placeables[0].place(0.ipx, 0.ipx)
@@ -366,11 +365,11 @@
Padding(size = size) {
WithConstraints { constraints ->
paddedConstraints.value = constraints
- Layout(layoutBlock = { _, childConstraints ->
+ Layout(measureBlock = { _, childConstraints ->
firstChildConstraints.value = childConstraints
layout(size, size) { }
}, children = { })
- Layout(layoutBlock = { _, chilConstraints ->
+ Layout(measureBlock = { _, chilConstraints ->
secondChildConstraints.value = chilConstraints
layout(size, size) { }
}, children = { })
@@ -424,23 +423,23 @@
activityTestRule.runOnUiThreadIR {
activity.setContent {
val header = @Composable {
- Layout(layoutBlock = { _, constraints ->
+ Layout(measureBlock = { _, constraints ->
assertEquals(childConstraints[0], constraints)
layout(0.ipx, 0.ipx) {}
}, children = {})
}
val footer = @Composable {
- Layout(layoutBlock = { _, constraints ->
+ Layout(measureBlock = { _, constraints ->
assertEquals(childConstraints[1], constraints)
layout(0.ipx, 0.ipx) {}
}, children = {})
- Layout(layoutBlock = { _, constraints ->
+ Layout(measureBlock = { _, constraints ->
assertEquals(childConstraints[2], constraints)
layout(0.ipx, 0.ipx) {}
}, children = {})
}
@Suppress("USELESS_CAST")
- Layout(childrenArray = arrayOf(header, footer)) { measurables, _ ->
+ Layout(header, footer) { measurables, _ ->
assertEquals(childrenCount, measurables.size)
measurables.forEachIndexed { index, measurable ->
measurable.measure(childConstraints[index])
@@ -462,16 +461,16 @@
activity.setContent {
val header = @Composable {
ParentData(data = 0) {
- Layout(layoutBlock = { _, _ -> layout(0.ipx, 0.ipx, {}) }, children = {})
+ Layout(measureBlock = { _, _ -> layout(0.ipx, 0.ipx, {}) }, children = {})
}
}
val footer = @Composable {
ParentData(data = 1) {
- Layout(layoutBlock = { _, _ -> layout(0.ipx, 0.ipx, {}) }, children = {})
+ Layout(measureBlock = { _, _ -> layout(0.ipx, 0.ipx, {}) }, children = {})
}
}
- Layout(childrenArray = arrayOf(header, footer)) { measurables, _ ->
+ Layout(header, footer) { measurables, _ ->
assertEquals(0, measurables[0].parentData)
assertEquals(1, measurables[1].parentData)
layout(0.ipx, 0.ipx, {})
@@ -522,7 +521,7 @@
canvas.drawRect(parentSize.toRect(), paint)
}
}
- }, layoutBlock = { measurables, constraints ->
+ }, measureBlock = { measurables, constraints ->
measureCalls++
layout(30.ipx, 30.ipx) {
layoutCalls++
@@ -560,7 +559,7 @@
height: IntPx,
children: @Composable() () -> Unit
) {
- Layout(children = children, layoutBlock = { measurables, constraints ->
+ Layout(children = children, measureBlock = { measurables, constraints ->
val resolvedWidth = width.coerceIn(constraints.minWidth, constraints.maxWidth)
val resolvedHeight = height.coerceIn(constraints.minHeight, constraints.maxHeight)
layout(resolvedWidth, resolvedHeight) {
@@ -596,7 +595,7 @@
drawn.value = true
latch.countDown()
}
- }, layoutBlock = { _, constraints ->
+ }, measureBlock = { _, constraints ->
measured.value = true
val resolvedWidth = width.coerceIn(constraints.minWidth, constraints.maxWidth)
val resolvedHeight = constraints.minHeight
@@ -943,7 +942,7 @@
@Composable
fun AtLeastSize(size: IntPx, children: @Composable() () -> Unit) {
Layout(
- layoutBlock = { measurables, constraints ->
+ measureBlock = { measurables, constraints ->
val newConstraints = Constraints(
minWidth = max(size, constraints.minWidth),
maxWidth = max(size, constraints.maxWidth),
@@ -971,7 +970,7 @@
@Composable
fun Align(children: @Composable() () -> Unit) {
Layout(
- layoutBlock = { measurables, constraints ->
+ measureBlock = { measurables, constraints ->
val newConstraints = Constraints(
minWidth = IntPx.Zero,
maxWidth = constraints.maxWidth,
@@ -999,7 +998,7 @@
@Composable
fun Padding(size: IntPx, children: @Composable() () -> Unit) {
Layout(
- layoutBlock = { measurables, constraints ->
+ measureBlock = { measurables, constraints ->
val totalDiff = size * 2
val newConstraints = Constraints(
minWidth = (constraints.minWidth - totalDiff).coerceAtLeast(0.ipx),
diff --git a/ui/ui-framework/src/androidTest/java/androidx/ui/core/test/ParentDataTest.kt b/ui/ui-framework/src/androidTest/java/androidx/ui/core/test/ParentDataTest.kt
index c8de633..196df60 100644
--- a/ui/ui-framework/src/androidTest/java/androidx/ui/core/test/ParentDataTest.kt
+++ b/ui/ui-framework/src/androidTest/java/androidx/ui/core/test/ParentDataTest.kt
@@ -65,7 +65,7 @@
activity.setContent {
Layout(children = {
SimpleDrawChild(drawLatch = drawLatch)
- }, layoutBlock = { measurables, constraints ->
+ }, measureBlock = { measurables, constraints ->
assertEquals(1, measurables.size)
parentData.value = measurables[0].parentData
@@ -90,7 +90,7 @@
ParentData(data = "Hello") {
SimpleDrawChild(drawLatch = drawLatch)
}
- }, layoutBlock = { measurables, constraints ->
+ }, measureBlock = { measurables, constraints ->
assertEquals(1, measurables.size)
parentData.value = measurables[0].parentData
@@ -115,7 +115,7 @@
ParentData(data = "Hello") {
Layout(children = {
SimpleDrawChild(drawLatch = drawLatch)
- }, layoutBlock = { measurables, constraints ->
+ }, measureBlock = { measurables, constraints ->
assertEquals(1, measurables.size)
parentData.value = measurables[0].parentData
diff --git a/ui/ui-framework/src/androidTest/java/androidx/ui/core/test/TextFieldTest.kt b/ui/ui-framework/src/androidTest/java/androidx/ui/core/test/TextFieldTest.kt
new file mode 100644
index 0000000..c5d139f
--- /dev/null
+++ b/ui/ui-framework/src/androidTest/java/androidx/ui/core/test/TextFieldTest.kt
@@ -0,0 +1,68 @@
+/*
+ * 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.test
+
+import androidx.compose.composer
+import androidx.compose.state
+import androidx.compose.unaryPlus
+import androidx.test.filters.SmallTest
+import androidx.ui.core.FocusManagerAmbient
+import androidx.ui.core.TestTag
+import androidx.ui.core.TextField
+import androidx.ui.core.input.FocusManager
+import androidx.ui.input.EditorModel
+import androidx.ui.input.EditorStyle
+import androidx.ui.test.createComposeRule
+import androidx.ui.test.doClick
+import androidx.ui.test.findByTag
+import com.nhaarman.mockitokotlin2.any
+import com.nhaarman.mockitokotlin2.mock
+import com.nhaarman.mockitokotlin2.times
+import com.nhaarman.mockitokotlin2.verify
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+
+@SmallTest
+@RunWith(JUnit4::class)
+class TextFieldTest {
+ @get:Rule
+ val composeTestRule = createComposeRule()
+
+ @Test
+ fun textField_focusInSemantics() {
+ val focusManager = mock<FocusManager>()
+ composeTestRule.setContent {
+ val state = +state { EditorModel() }
+ FocusManagerAmbient.Provider(value = focusManager) {
+ TestTag(tag = "textField") {
+ TextField(
+ value = state.value,
+ onValueChange = { state.value = it },
+ editorStyle = EditorStyle()
+ )
+ }
+ }
+ }
+
+ findByTag("textField")
+ .doClick()
+
+ verify(focusManager, times(1)).requestFocus(any())
+ }
+}
\ No newline at end of file
diff --git a/ui/ui-framework/src/androidTest/java/androidx/ui/core/test/TextLayoutTest.kt b/ui/ui-framework/src/androidTest/java/androidx/ui/core/test/TextLayoutTest.kt
index ccfa929..8e5c1d9 100644
--- a/ui/ui-framework/src/androidTest/java/androidx/ui/core/test/TextLayoutTest.kt
+++ b/ui/ui-framework/src/androidTest/java/androidx/ui/core/test/TextLayoutTest.kt
@@ -16,7 +16,6 @@
package androidx.ui.core.test
-import androidx.compose.Children
import androidx.compose.Composable
import androidx.compose.composer
import androidx.test.filters.SmallTest
@@ -130,7 +129,7 @@
Text("aaaa", style = TextStyle(fontFamily = fontFamily))
}
ComplexLayout(text) {
- layout { measurables, _ ->
+ measure { measurables, _ ->
val textMeasurable = measurables.first()
// Min width.
@@ -154,7 +153,7 @@
intrinsicsLatch.countDown()
- layoutResult(0.ipx, 0.ipx) {}
+ layout(0.ipx, 0.ipx) {}
}
minIntrinsicWidth { _, _ -> 0.ipx }
minIntrinsicHeight { _, _ -> 0.ipx }
@@ -184,4 +183,4 @@
}
activityTestRule.runOnUiThread(runnable)
}
-}
\ No newline at end of file
+}
diff --git a/ui/ui-framework/src/main/java/androidx/ui/core/Layout.kt b/ui/ui-framework/src/main/java/androidx/ui/core/Layout.kt
index 4875fe2..b1f1b1c 100644
--- a/ui/ui-framework/src/main/java/androidx/ui/core/Layout.kt
+++ b/ui/ui-framework/src/main/java/androidx/ui/core/Layout.kt
@@ -16,7 +16,6 @@
package androidx.ui.core
-import androidx.compose.Children
import androidx.compose.Composable
import androidx.compose.Compose
import androidx.compose.ambient
@@ -217,7 +216,7 @@
* Receiver scope for the [ComplexLayout] lambda.
*/
class ComplexLayoutReceiver internal constructor(internal val layoutState: ComplexLayoutState) {
- fun layout(layoutBlock: ComplexLayoutBlock) {
+ fun measure(layoutBlock: ComplexLayoutBlock) {
layoutState.layoutBlock = layoutBlock
}
fun minIntrinsicWidth(minIntrinsicWidthBlock: IntrinsicMeasurementBlock) {
@@ -325,13 +324,13 @@
this as ComplexLayoutState
return this.measure(constraints)
}
- fun layoutResult(
+ fun layout(
width: IntPx,
height: IntPx,
- block: PositioningBlockReceiver.() -> Unit
+ positioningBlock: PositioningBlockReceiver.() -> Unit
): LayoutResult {
layoutState.resize(width, height)
- layoutState.positioningBlock = block
+ layoutState.positioningBlock = positioningBlock
return LayoutResult.instance
}
fun Measurable.minIntrinsicWidth(h: IntPx) =
@@ -359,7 +358,7 @@
@Composable
fun Layout(
children: @Composable() () -> Unit,
- layoutBlock: LayoutBlock
+ measureBlock: LayoutBlock
) {
trace("UI:Layout") {
ComplexLayout(children = children, block = {
@@ -368,10 +367,10 @@
LayoutMeasure, /* measure lambda */
{ _, _, _ -> }
)
- layout { measurables, constraints ->
+ measure { measurables, constraints ->
layoutReceiver.complexMeasure = LayoutMeasure
- layoutReceiver.complexLayoutResult = { w, h, block -> layoutResult(w, h, block) }
- layoutReceiver.layoutBlock(measurables, constraints)
+ layoutReceiver.complexLayoutResult = { w, h, block -> layout(w, h, block) }
+ layoutReceiver.measureBlock(measurables, constraints)
}
minIntrinsicWidth { measurables, h ->
@@ -382,7 +381,7 @@
}
layoutReceiver.complexLayoutResult = { width, _, _ -> intrinsicWidth = width }
val constraints = Constraints(maxHeight = h)
- layoutReceiver.layoutBlock(measurables, constraints)
+ layoutReceiver.measureBlock(measurables, constraints)
intrinsicWidth
}
@@ -394,7 +393,7 @@
}
layoutReceiver.complexLayoutResult = { width, _, _ -> intrinsicWidth = width }
val constraints = Constraints(maxHeight = h)
- layoutReceiver.layoutBlock(measurables, constraints)
+ layoutReceiver.measureBlock(measurables, constraints)
intrinsicWidth
}
@@ -406,7 +405,7 @@
}
layoutReceiver.complexLayoutResult = { _, height, _ -> intrinsicHeight = height }
val constraints = Constraints(maxWidth = w)
- layoutReceiver.layoutBlock(measurables, constraints)
+ layoutReceiver.measureBlock(measurables, constraints)
intrinsicHeight
}
@@ -418,7 +417,7 @@
}
layoutReceiver.complexLayoutResult = { _, height, _ -> intrinsicHeight = height }
val constraints = Constraints(maxWidth = w)
- layoutReceiver.layoutBlock(measurables, constraints)
+ layoutReceiver.measureBlock(measurables, constraints)
intrinsicHeight
}
})
@@ -450,12 +449,12 @@
*/
@Composable
fun Layout(
- childrenArray: Array<@Composable() () -> Unit>,
- layoutBlock: LayoutBlock
+ vararg childrenArray: @Composable() () -> Unit,
+ measureBlock: LayoutBlock
) {
val ChildrenEndMarker = @Composable { children: @Composable() () -> Unit ->
ParentData(data = ChildrenEndParentData(children)) {
- Layout(layoutBlock={_, _ -> layout(0.ipx, 0.ipx){}}, children = {})
+ Layout(measureBlock={_, _ -> layout(0.ipx, 0.ipx){}}, children = {})
}
}
val children = @Composable {
@@ -466,7 +465,7 @@
}
}
- Layout(layoutBlock = layoutBlock, children = children)
+ Layout(measureBlock = measureBlock, children = children)
}
/**
@@ -503,9 +502,9 @@
fun layout(
width: IntPx,
height: IntPx,
- block: PositioningBlockReceiver.() -> Unit
+ positioningBlock: PositioningBlockReceiver.() -> Unit
): LayoutResult {
- complexLayoutResult(width, height, block)
+ complexLayoutResult(width, height, positioningBlock)
return LayoutResult.instance
}
}
@@ -538,33 +537,31 @@
val ref = +compositionReference()
val context = +ambient(ContextAmbient)
- Layout(
- layoutBlock = { _, constraints ->
- val root = layoutState.layoutNode
- // Start subcomposition from the current node.
- Compose.composeInto(
- root,
- context,
- ref
- ) {
- children(p1 = constraints)
- }
+ Layout(children = {}) { _, constraints ->
+ val root = layoutState.layoutNode
+ // Start subcomposition from the current node.
+ Compose.composeInto(
+ root,
+ context,
+ ref
+ ) {
+ children(p1 = constraints)
+ }
- // Measure the obtained children and compute our size.
- val measurables = layoutState.childrenMeasurables
- val placeables = measurables.map { it.measure(constraints) }
- val layoutSize = constraints.constrain(IntPxSize(
- placeables.map { it.width }.maxBy { it.value } ?: IntPx.Zero,
- placeables.map { it.height }.maxBy { it.value } ?: IntPx.Zero
- ))
+ // Measure the obtained children and compute our size.
+ val measurables = layoutState.childrenMeasurables
+ val placeables = measurables.map { it.measure(constraints) }
+ val layoutSize = constraints.constrain(IntPxSize(
+ placeables.map { it.width }.maxBy { it.value } ?: IntPx.Zero,
+ placeables.map { it.height }.maxBy { it.value } ?: IntPx.Zero
+ ))
- layout(layoutSize.width, layoutSize.height) {
- placeables.forEach { placeable ->
- placeable.place(IntPx.Zero, IntPx.Zero)
- }
+ layout(layoutSize.width, layoutSize.height) {
+ placeables.forEach { placeable ->
+ placeable.place(IntPx.Zero, IntPx.Zero)
}
- },
- children={})
+ }
+ }
}
private val OnPositionedKey = DataNodeKey<(LayoutCoordinates) -> Unit>("Compose:OnPositioned")
diff --git a/ui/ui-framework/src/main/java/androidx/ui/core/Text.kt b/ui/ui-framework/src/main/java/androidx/ui/core/Text.kt
index eaf59ee..c389c6e 100644
--- a/ui/ui-framework/src/main/java/androidx/ui/core/Text.kt
+++ b/ui/ui-framework/src/main/java/androidx/ui/core/Text.kt
@@ -37,8 +37,8 @@
import androidx.ui.core.selection.TextSelectionProcessor
import androidx.ui.semantics.Semantics
import androidx.ui.semantics.accessibilityLabel
-import androidx.ui.text.TextSelection
-import androidx.ui.text.TextPainter
+import androidx.ui.text.TextDelegate
+import androidx.ui.text.TextRange
import androidx.ui.text.TextSpan
import androidx.ui.text.TextStyle
import androidx.ui.text.style.TextAlign
@@ -190,7 +190,7 @@
*/
selectionColor: Color = DefaultSelectionColor
) {
- val internalSelection = +state<TextSelection?> { null }
+ val internalSelection = +state<TextRange?> { null }
val registrar = +ambient(SelectionRegistrarAmbient)
val layoutCoordinates = +state<LayoutCoordinates?> { null }
@@ -205,7 +205,7 @@
accessibilityLabel = text.text
}
) {
- val textPainter = +memo(
+ val textDelegate = +memo(
text,
mergedStyle,
paragraphStyle,
@@ -214,7 +214,7 @@
maxLines,
density
) {
- TextPainter(
+ TextDelegate(
text = text,
style = mergedStyle,
paragraphStyle = paragraphStyle,
@@ -232,35 +232,35 @@
OnPositioned(onPositioned = { layoutCoordinates.value = it })
Draw { canvas, _ ->
internalSelection.value?.let {
- textPainter.paintBackground(
+ textDelegate.paintBackground(
it.start, it.end, selectionColor, canvas
)
}
- textPainter.paint(canvas)
+ textDelegate.paint(canvas)
}
}
ComplexLayout(children) {
- layout { _, constraints ->
- textPainter.layout(constraints)
- layoutResult(textPainter.width.px.round(), textPainter.height.px.round()) {}
+ measure { _, constraints ->
+ textDelegate.layout(constraints)
+ layout(textDelegate.width.px.round(), textDelegate.height.px.round()) {}
}
minIntrinsicWidth { _, _ ->
// TODO(popam): discuss with the Text team about this
throw UnsupportedOperationException()
- // textPainter.layout(Constraints(0.ipx, IntPx.Infinity, 0.ipx, h))
- // textPainter.minIntrinsicWidth.px.round()
+ // textDelegate.layout(Constraints(0.ipx, IntPx.Infinity, 0.ipx, h))
+ // textDelegate.minIntrinsicWidth.px.round()
}
minIntrinsicHeight { _, w ->
- textPainter.layout(Constraints(0.ipx, w, 0.ipx, IntPx.Infinity))
- textPainter.height.px.round()
+ textDelegate.layout(Constraints(0.ipx, w, 0.ipx, IntPx.Infinity))
+ textDelegate.height.px.round()
}
maxIntrinsicWidth { _, h ->
- textPainter.layout(Constraints(0.ipx, IntPx.Infinity, 0.ipx, h))
- textPainter.maxIntrinsicWidth.px.round()
+ textDelegate.layout(Constraints(0.ipx, IntPx.Infinity, 0.ipx, h))
+ textDelegate.maxIntrinsicWidth.px.round()
}
maxIntrinsicHeight { _, w ->
- textPainter.layout(Constraints(0.ipx, w, 0.ipx, IntPx.Infinity))
- textPainter.height.px.round()
+ textDelegate.layout(Constraints(0.ipx, w, 0.ipx, IntPx.Infinity))
+ textDelegate.height.px.round()
}
}
@@ -290,7 +290,7 @@
selectionCoordinates = Pair(startPx, endPx),
mode = mode,
onSelectionChange = { internalSelection.value = it },
- textPainter = textPainter
+ textDelegate = textDelegate
)
if (!textSelectionProcessor.isSelected) return null
diff --git a/ui/ui-framework/src/main/java/androidx/ui/core/TextField.kt b/ui/ui-framework/src/main/java/androidx/ui/core/TextField.kt
index 8efe4e7..f329735 100644
--- a/ui/ui-framework/src/main/java/androidx/ui/core/TextField.kt
+++ b/ui/ui-framework/src/main/java/androidx/ui/core/TextField.kt
@@ -16,7 +16,6 @@
package androidx.ui.core
import androidx.compose.composer
-import androidx.compose.Children
import androidx.compose.Composable
import androidx.compose.ambient
import androidx.compose.memo
@@ -26,42 +25,21 @@
import androidx.ui.core.gesture.DragObserver
import androidx.ui.core.gesture.PressGestureDetector
import androidx.ui.core.input.FocusManager
-import androidx.ui.graphics.Color
import androidx.ui.input.EditProcessor
import androidx.ui.input.EditorModel
+import androidx.ui.input.EditorStyle
import androidx.ui.input.ImeAction
import androidx.ui.input.KeyboardType
-import androidx.ui.text.TextPainter
-import androidx.ui.text.TextStyle
-
-/**
- * Data class holding text display attributes used for editors.
- */
-data class EditorStyle(
- /** The editor text style */
- val textStyle: TextStyle? = null,
-
- /**
- * The composition background color
- *
- * @see EditorModel.composition
- */
- val compositionColor: Color = Color(alpha = 0xFF, red = 0xB0, green = 0xE0, blue = 0xE6),
-
- /**
- * The selection background color
- *
- * @see EditorModel.selection
- */
- // TODO(nona): share with Text.DEFAULT_SELECTION_COLOR
- val selectionColor: Color = Color(alpha = 0x66, red = 0x33, green = 0xB5, blue = 0xE5)
-)
+import androidx.ui.input.VisualTransformation
+import androidx.ui.semantics.Semantics
+import androidx.ui.semantics.onClick
+import androidx.ui.text.TextDelegate
/**
* A default implementation of TextField
*
* To make TextField work with platoform input service, you must keep the editor state and update
- * in [onValueChagne] callback.
+ * in [onValueChange] callback.
*
* Example:
* var state = +state { EditorModel() }
@@ -124,9 +102,9 @@
val (visualText, offsetMap) = +memo(value, visualTransformation) {
TextFieldDelegate.applyVisualFilter(value, visualTransformation)
}
- val textPainter = +memo(visualText, mergedStyle, density, resourceLoader) {
+ val textDelegate = +memo(visualText, mergedStyle, density, resourceLoader) {
// TODO(nona): Add parameter for text direction, softwrap, etc.
- TextPainter(
+ TextDelegate(
text = visualText,
style = mergedStyle,
density = density,
@@ -155,7 +133,7 @@
textInputService?.let { textInputService ->
TextFieldDelegate.notifyFocusedRect(
value,
- textPainter,
+ textDelegate,
coords,
textInputService,
hasFocus.value,
@@ -177,7 +155,7 @@
onRelease = {
TextFieldDelegate.onRelease(
it,
- textPainter,
+ textDelegate,
processor,
offsetMap,
onValueChange,
@@ -194,7 +172,7 @@
coords.value = it
TextFieldDelegate.notifyFocusedRect(
value,
- textPainter,
+ textDelegate,
it,
textInputService,
hasFocus.value,
@@ -206,12 +184,12 @@
canvas,
value,
offsetMap,
- textPainter,
+ textDelegate,
hasFocus.value,
editorStyle) }
},
- layoutBlock = { _, constraints ->
- TextFieldDelegate.layout(textPainter, constraints).let {
+ measureBlock = { _, constraints ->
+ TextFieldDelegate.layout(textDelegate, constraints).let {
layout(it.first, it.second) {}
}
}
@@ -234,28 +212,40 @@
val focused = +state { false }
val focusManager = +ambient(FocusManagerAmbient)
- DragPositionGestureDetector(
- onPress = {
- if (focused.value) {
- onPress(it)
- } else {
- focusManager.requestFocus(object : FocusManager.FocusNode {
- override fun onFocus() {
- onFocus()
- focused.value = true
- }
+ val doFocusIn = {
+ if (!focused.value) {
+ focusManager.requestFocus(object : FocusManager.FocusNode {
+ override fun onFocus() {
+ onFocus()
+ focused.value = true
+ }
- override fun onBlur() {
- onBlur()
- focused.value = false
- }
- })
- }
- },
- onDragAt = onDragAt,
- onRelease = onRelease
+ override fun onBlur() {
+ onBlur()
+ focused.value = false
+ }
+ })
+ }
+ }
+
+ Semantics(
+ properties = {
+ onClick(action = doFocusIn)
+ }
) {
- children()
+ DragPositionGestureDetector(
+ onPress = {
+ if (focused.value) {
+ onPress(it)
+ } else {
+ doFocusIn()
+ }
+ },
+ onDragAt = onDragAt,
+ onRelease = onRelease
+ ) {
+ children()
+ }
}
}
diff --git a/ui/ui-framework/src/main/java/androidx/ui/core/TextFieldDelegate.kt b/ui/ui-framework/src/main/java/androidx/ui/core/TextFieldDelegate.kt
index 9d66efa..bd7108d 100644
--- a/ui/ui-framework/src/main/java/androidx/ui/core/TextFieldDelegate.kt
+++ b/ui/ui-framework/src/main/java/androidx/ui/core/TextFieldDelegate.kt
@@ -21,48 +21,85 @@
import androidx.ui.input.EditOperation
import androidx.ui.input.EditProcessor
import androidx.ui.input.EditorModel
+import androidx.ui.input.EditorStyle
import androidx.ui.input.FinishComposingTextEditOp
import androidx.ui.input.ImeAction
import androidx.ui.input.KeyboardType
+import androidx.ui.input.OffsetMap
import androidx.ui.input.SetSelectionEditOp
import androidx.ui.input.TextInputService
+import androidx.ui.input.TransformedText
+import androidx.ui.input.VisualTransformation
+import androidx.ui.input.identityOffsetMap
import androidx.ui.painting.Canvas
import androidx.ui.text.AnnotatedString
-import androidx.ui.text.TextPainter
+import androidx.ui.text.Paragraph
+import androidx.ui.text.ParagraphConstraints
+import androidx.ui.text.ParagraphStyle
+import androidx.ui.text.TextDelegate
+import androidx.ui.text.TextStyle
+import androidx.ui.text.font.Font
+import kotlin.math.roundToInt
+
+/**
+ * Computed the line height for the empty TextField.
+ *
+ * The bounding box or x-advance of the empty text is empty, i.e. 0x0 box or 0px advance. However
+ * this is not useful for TextField since text field want to reserve some amount of height for
+ * accepting touch for starting text input. In Android, uses FontMetrics of the first font in the
+ * fallback chain to compute this height, this is because custom font may have different
+ * ascender/descender from the default font in Android.
+ *
+ * Until we have font metrics APIs, use the height of reference text as a workaround.
+ *
+ * TODO(nona): Add FontMetrics API and stop doing this workaround.
+ */
+private fun computeLineHeightForEmptyText(
+ textStyle: TextStyle,
+ density: Density,
+ resourceLoader: Font.ResourceLoader
+): IntPx {
+ return Paragraph(
+ text = "H", // No meaning: just a reference character.
+ style = textStyle,
+ paragraphStyle = ParagraphStyle(),
+ textStyles = listOf(),
+ maxLines = 1,
+ ellipsis = false,
+ density = density,
+ resourceLoader = resourceLoader
+ ).apply {
+ layout(ParagraphConstraints(width = Float.POSITIVE_INFINITY))
+ }.height.roundToInt().ipx
+}
internal class TextFieldDelegate {
companion object {
/**
* Process text layout with given constraint.
*
- * @param textPainter The text painter
+ * @param textDelegate The text painter
* @param constraints The layout constraints
* @return the bounding box size(width and height) of the layout result
*/
@JvmStatic
- fun layout(textPainter: TextPainter, constraints: Constraints): Pair<IntPx, IntPx> {
- val isEmptyText = textPainter.text?.text?.isEmpty() ?: true
- val activeTextPainter = if (isEmptyText) {
- // Even with empty text, edit filed must have at least non-zero height widget. Use
- // "H" height for this empty text height.
- TextPainter(
- text = AnnotatedString(text = "H"),
- style = textPainter.style,
- density = textPainter.density,
- resourceLoader = textPainter.resourceLoader
- ).apply {
- layout(constraints)
- }
- } else {
- textPainter
- }
+ fun layout(textDelegate: TextDelegate, constraints: Constraints): Pair<IntPx, IntPx> {
// We anyway need to compute layout for preventing NPE during draw which require layout
// result.
// TODO(nona): Fix this?
- textPainter.layout(constraints)
+ textDelegate.layout(Constraints.tightConstraintsForWidth(constraints.maxWidth))
- val height = activeTextPainter.height.px.round()
+ val isEmptyText = textDelegate.text.text.isEmpty()
+ val height = if (isEmptyText) {
+ computeLineHeightForEmptyText(
+ textStyle = textDelegate.textStyle,
+ density = textDelegate.density,
+ resourceLoader = textDelegate.resourceLoader
+ )
+ } else {
+ textDelegate.height.px.round()
+ }
val width = constraints.maxWidth
return Pair(width, height)
}
@@ -73,7 +110,7 @@
* @param canvas The target canvas.
* @param value The editor state
* @param offsetMap The offset map
- * @param textPainter The text painter
+ * @param textDelegate The text painter
* @param hasFocus true if this widget is focused, otherwise false
* @param editorStyle The editor style.
*/
@@ -82,12 +119,12 @@
canvas: Canvas,
value: EditorModel,
offsetMap: OffsetMap,
- textPainter: TextPainter,
+ textDelegate: TextDelegate,
hasFocus: Boolean,
editorStyle: EditorStyle
) {
value.composition?.let {
- textPainter.paintBackground(
+ textDelegate.paintBackground(
offsetMap.originalToTransformed(it.start),
offsetMap.originalToTransformed(it.end),
editorStyle.compositionColor,
@@ -96,18 +133,18 @@
}
if (value.selection.collapsed) {
if (hasFocus) {
- textPainter.paintCursor(
+ textDelegate.paintCursor(
offsetMap.originalToTransformed(value.selection.start), canvas)
}
} else {
- textPainter.paintBackground(
+ textDelegate.paintBackground(
offsetMap.originalToTransformed(value.selection.start),
offsetMap.originalToTransformed(value.selection.end),
editorStyle.selectionColor,
canvas
)
}
- textPainter.paint(canvas)
+ textDelegate.paint(canvas)
}
/**
@@ -118,7 +155,7 @@
@JvmStatic
fun notifyFocusedRect(
value: EditorModel,
- textPainter: TextPainter,
+ textDelegate: TextDelegate,
layoutCoordinates: LayoutCoordinates,
textInputService: TextInputService,
hasFocus: Boolean,
@@ -129,11 +166,17 @@
}
val bbox = if (value.selection.end < value.text.length) {
- textPainter.getBoundingBox(offsetMap.originalToTransformed(value.selection.end))
+ textDelegate.getBoundingBox(offsetMap.originalToTransformed(value.selection.end))
} else if (value.selection.end != 0) {
- textPainter.getBoundingBox(offsetMap.originalToTransformed(value.selection.end) - 1)
+ textDelegate.getBoundingBox(
+ offsetMap.originalToTransformed(value.selection.end) - 1)
} else {
- Rect(0f, 0f, 1.0f, textPainter.preferredLineHeight)
+ val lineHeightForEmptyText = computeLineHeightForEmptyText(
+ textDelegate.textStyle,
+ textDelegate.density,
+ textDelegate.resourceLoader
+ )
+ Rect(0f, 0f, 1.0f, lineHeightForEmptyText.value.toFloat())
}
val globalLT = layoutCoordinates.localToRoot(PxPosition(bbox.left.px, bbox.top.px))
@@ -178,7 +221,7 @@
* Called when onRelease event is fired.
*
* @param position The event position in widget coordinate.
- * @param textPainter The text painter
+ * @param textDelegate The text painter
* @param editProcessor The edit processor
* @param offsetMap The offset map
* @param onValueChange The callback called when the new editor state arrives.
@@ -188,7 +231,7 @@
@JvmStatic
fun onRelease(
position: PxPosition,
- textPainter: TextPainter,
+ textDelegate: TextDelegate,
editProcessor: EditProcessor,
offsetMap: OffsetMap,
onValueChange: (EditorModel) -> Unit,
@@ -198,7 +241,7 @@
textInputService?.showSoftwareKeyboard()
if (hasFocus) {
val offset = offsetMap.transformedToOriginal(
- textPainter.getOffsetForPosition(position))
+ textDelegate.getOffsetForPosition(position))
onEditCommand(
listOf(SetSelectionEditOp(offset, offset)),
editProcessor,
diff --git a/ui/ui-framework/src/main/java/androidx/ui/core/Wrapper.kt b/ui/ui-framework/src/main/java/androidx/ui/core/Wrapper.kt
index fd403be..0b7d2f0 100644
--- a/ui/ui-framework/src/main/java/androidx/ui/core/Wrapper.kt
+++ b/ui/ui-framework/src/main/java/androidx/ui/core/Wrapper.kt
@@ -153,6 +153,18 @@
// with nested AndroidCraneView case
val focusManager = +memo { FocusManager() }
val configuration = +state { context.applicationContext.resources.configuration }
+
+ // We don't use the attached View's layout direction here since that layout direction may not
+ // be resolved since the widgets may be composed without attaching to the RootViewImpl.
+ // In Jetpack Compose, use the locale layout direction (i.e. layoutDirection came from
+ // configuration) as a default layout direction.
+ val layoutDirection = when(configuration.value.layoutDirection) {
+ android.util.LayoutDirection.LTR -> LayoutDirection.Ltr
+ android.util.LayoutDirection.RTL -> LayoutDirection.Rtl
+ // API doc says Configuration#getLayoutDirection only returns LTR or RTL.
+ // Fallback to LTR for unexpected return value.
+ else -> LayoutDirection.Ltr
+ }
+memo {
craneView.configurationChangeObserver = {
// onConfigurationChange is the correct hook to update configuration, however it is
@@ -163,6 +175,7 @@
configuration.value = context.applicationContext.resources.configuration
}
}
+
ContextAmbient.Provider(value = context) {
CoroutineContextAmbient.Provider(value = coroutineContext) {
DensityAmbient.Provider(value = Density(context)) {
@@ -172,7 +185,9 @@
AutofillTreeAmbient.Provider(value = craneView.autofillTree) {
AutofillAmbient.Provider(value = craneView.autofill) {
ConfigurationAmbient.Provider(value = configuration.value) {
- content()
+ LayoutDirectionAmbient.Provider(value = layoutDirection) {
+ content()
+ }
}
}
}
@@ -197,6 +212,8 @@
// This will ultimately be replaced by Autofill Semantics (b/138604305).
val AutofillTreeAmbient = Ambient.of<AutofillTree>()
+val LayoutDirectionAmbient = Ambient.of<LayoutDirection>()
+
internal val FocusManagerAmbient = Ambient.of<FocusManager>()
internal val TextInputServiceAmbient = Ambient.of<TextInputService?>()
diff --git a/ui/ui-framework/src/main/java/androidx/ui/core/input/FocusManager.kt b/ui/ui-framework/src/main/java/androidx/ui/core/input/FocusManager.kt
index d354b8b..e0a2431 100644
--- a/ui/ui-framework/src/main/java/androidx/ui/core/input/FocusManager.kt
+++ b/ui/ui-framework/src/main/java/androidx/ui/core/input/FocusManager.kt
@@ -21,7 +21,7 @@
*
* TODO(nona): make this public?
*/
-internal class FocusManager {
+internal open class FocusManager {
/**
* An interface for focusable object
diff --git a/ui/ui-framework/src/main/java/androidx/ui/core/selection/SelectionContainer.kt b/ui/ui-framework/src/main/java/androidx/ui/core/selection/SelectionContainer.kt
index c5f5e95..a4e2b97 100644
--- a/ui/ui-framework/src/main/java/androidx/ui/core/selection/SelectionContainer.kt
+++ b/ui/ui-framework/src/main/java/androidx/ui/core/selection/SelectionContainer.kt
@@ -16,7 +16,6 @@
package androidx.ui.core.selection
-import androidx.compose.Children
import androidx.compose.Composable
import androidx.compose.composer
import androidx.compose.memo
@@ -65,41 +64,35 @@
children()
}
}
- Layout(children = content, layoutBlock = { measurables, constraints ->
+ Layout(content) { measurables, constraints ->
val placeable = measurables.firstOrNull()?.measure(constraints)
val width = placeable?.width ?: constraints.minWidth
val height = placeable?.height ?: constraints.minHeight
layout(width, height) {
placeable?.place(0.ipx, 0.ipx)
}
- })
+ }
}
val startHandle = @Composable {
TouchSlopDragGestureDetector(
dragObserver = manager.handleDragObserver(dragStartHandle = true)
) {
- Layout(
- children = { LeftPointingSelectionHandle() },
- layoutBlock = { _, constraints ->
- layout(constraints.minWidth, constraints.minHeight) {}
- })
+ Layout(children = { LeftPointingSelectionHandle() }) { _, constraints ->
+ layout(constraints.minWidth, constraints.minHeight) {}
+ }
}
}
val endHandle = @Composable {
TouchSlopDragGestureDetector(
dragObserver = manager.handleDragObserver(dragStartHandle = false)
) {
- Layout(
- children = { RightPointingSelectionHandle() },
- layoutBlock = { _, constraints ->
- layout(constraints.minWidth, constraints.minHeight) {}
- })
+ Layout(children = { RightPointingSelectionHandle() }) { _, constraints ->
+ layout(constraints.minWidth, constraints.minHeight) {}
+ }
}
}
@Suppress("USELESS_CAST")
- (Layout(
- childrenArray = arrayOf(content, startHandle, endHandle),
- layoutBlock = { measurables, constraints ->
+ Layout(content, startHandle, endHandle) { measurables, constraints ->
val placeable = measurables[0].measure(constraints)
val width = placeable.width
val height = placeable.height
@@ -135,6 +128,6 @@
end.place(endOffset.x, endOffset.y)
}
}
- }))
+ }
}
}
diff --git a/ui/ui-framework/src/main/java/androidx/ui/core/selection/SelectionMode.kt b/ui/ui-framework/src/main/java/androidx/ui/core/selection/SelectionMode.kt
index b405cb2..ba90523 100644
--- a/ui/ui-framework/src/main/java/androidx/ui/core/selection/SelectionMode.kt
+++ b/ui/ui-framework/src/main/java/androidx/ui/core/selection/SelectionMode.kt
@@ -18,7 +18,7 @@
import androidx.ui.core.PxPosition
import androidx.ui.core.px
-import androidx.ui.text.TextPainter
+import androidx.ui.text.TextDelegate
/**
* The enum class allows user to decide the selection mode.
@@ -31,14 +31,14 @@
*/
Vertical {
override fun isSelected(
- textPainter: TextPainter,
+ textDelegate: TextDelegate,
start: PxPosition,
end: PxPosition
): Boolean {
val top = 0.px
- val bottom = textPainter.height.px
+ val bottom = textDelegate.height.px
val left = 0.px
- val right = textPainter.width.px
+ val right = textDelegate.width.px
// When the end of the selection is above the top of the widget, the widget is outside
// of the selection range.
@@ -67,14 +67,14 @@
*/
Horizontal {
override fun isSelected(
- textPainter: TextPainter,
+ textDelegate: TextDelegate,
start: PxPosition,
end: PxPosition
): Boolean {
val top = 0.px
- val bottom = textPainter.height.px
+ val bottom = textDelegate.height.px
val left = 0.px
- val right = textPainter.width.px
+ val right = textDelegate.width.px
// When the end of the selection is on the left of the widget, the widget is outside of
// the selection range.
@@ -97,7 +97,7 @@
};
internal abstract fun isSelected(
- textPainter: TextPainter,
+ textDelegate: TextDelegate,
start: PxPosition,
end: PxPosition
): Boolean
diff --git a/ui/ui-framework/src/main/java/androidx/ui/core/selection/TextSelectionProcessor.kt b/ui/ui-framework/src/main/java/androidx/ui/core/selection/TextSelectionProcessor.kt
index 23419ed..6a8915d 100644
--- a/ui/ui-framework/src/main/java/androidx/ui/core/selection/TextSelectionProcessor.kt
+++ b/ui/ui-framework/src/main/java/androidx/ui/core/selection/TextSelectionProcessor.kt
@@ -18,8 +18,8 @@
import androidx.ui.core.PxPosition
import androidx.ui.core.px
-import androidx.ui.text.TextSelection
-import androidx.ui.text.TextPainter
+import androidx.ui.text.TextDelegate
+import androidx.ui.text.TextRange
import kotlin.math.max
/**
@@ -33,9 +33,9 @@
val mode: SelectionMode,
/** The lambda contains certain behavior when selection changes. Currently this is for changing
* the selection used for drawing in Text widget. */
- var onSelectionChange: (TextSelection?) -> Unit = {},
- /** The TextPainter object from Text widget. */
- val textPainter: TextPainter
+ var onSelectionChange: (TextRange?) -> Unit = {},
+ /** The TextDelegate object from Text widget. */
+ val textDelegate: TextDelegate
) {
/**
* The coordinates of the graphical position for selection start character offset.
@@ -69,7 +69,7 @@
internal var isSelected = false
/** The length of the text in text widget. */
- private val length = textPainter.text?.let { it.text.length } ?: 0
+ private val length = textDelegate.text.text.length
init {
processTextSelection()
@@ -82,24 +82,24 @@
val startPx = selectionCoordinates.first
val endPx = selectionCoordinates.second
- if (!mode.isSelected(textPainter, start = startPx, end = endPx)) {
+ if (!mode.isSelected(textDelegate, start = startPx, end = endPx)) {
onSelectionChange(null)
return
}
isSelected = true
var (textSelectionStart, containsWholeSelectionStart) =
- getSelectionBorder(textPainter, startPx, true)
+ getSelectionBorder(textDelegate, startPx, true)
var (textSelectionEnd, containsWholeSelectionEnd) =
- getSelectionBorder(textPainter, endPx, false)
+ getSelectionBorder(textDelegate, endPx, false)
if (textSelectionStart == textSelectionEnd) {
- val wordBoundary = textPainter.getWordBoundary(textSelectionStart)
+ val wordBoundary = textDelegate.getWordBoundary(textSelectionStart)
textSelectionStart = wordBoundary.start
textSelectionEnd = wordBoundary.end
}
- onSelectionChange(TextSelection(textSelectionStart, textSelectionEnd))
+ onSelectionChange(TextRange(textSelectionStart, textSelectionEnd))
startCoordinates = getSelectionHandleCoordinates(textSelectionStart)
endCoordinates = getSelectionHandleCoordinates(textSelectionEnd)
@@ -113,7 +113,7 @@
* selection.
*/
private fun getSelectionBorder(
- textPainter: TextPainter,
+ textDelegate: TextDelegate,
// This position is in Text widget coordinate system.
position: PxPosition,
isStart: Boolean
@@ -127,9 +127,9 @@
var containsWholeSelectionBorder = false
val top = 0.px
- val bottom = textPainter.height.px
+ val bottom = textDelegate.height.px
val left = 0.px
- val right = textPainter.width.px
+ val right = textDelegate.width.px
// If the current text widget contains the whole selection's border, then find the exact
// character offset of the border, and the flag checking if the widget contains the whole
// selection's border will be set to true.
@@ -141,7 +141,7 @@
// Constrain the character offset of the selection border to be within the text range
// of the current widget.
val constrainedSelectionBorderOffset =
- textPainter.getOffsetForPosition(position).coerceIn(0, length - 1)
+ textDelegate.getOffsetForPosition(position).coerceIn(0, length - 1)
selectionBorder = constrainedSelectionBorderOffset
containsWholeSelectionBorder = true
}
@@ -149,10 +149,10 @@
}
private fun getSelectionHandleCoordinates(offset: Int): PxPosition {
- val left = textPainter.getPrimaryHorizontal(offset)
+ val left = textDelegate.getPrimaryHorizontal(offset)
- val line = textPainter.getLineForOffset(offset)
- val bottom = textPainter.getLineBottom(line)
+ val line = textDelegate.getLineForOffset(offset)
+ val bottom = textDelegate.getLineBottom(line)
return PxPosition(left.px, bottom.px)
}
diff --git a/ui/ui-framework/src/test/java/androidx/ui/core/TextFieldDelegateTest.kt b/ui/ui-framework/src/test/java/androidx/ui/core/TextFieldDelegateTest.kt
index 4657c23..179d4c5 100644
--- a/ui/ui-framework/src/test/java/androidx/ui/core/TextFieldDelegateTest.kt
+++ b/ui/ui-framework/src/test/java/androidx/ui/core/TextFieldDelegateTest.kt
@@ -22,14 +22,16 @@
import androidx.ui.input.EditOperation
import androidx.ui.input.EditProcessor
import androidx.ui.input.EditorModel
+import androidx.ui.input.EditorStyle
import androidx.ui.input.FinishComposingTextEditOp
import androidx.ui.input.ImeAction
import androidx.ui.input.KeyboardType
+import androidx.ui.input.OffsetMap
import androidx.ui.input.SetSelectionEditOp
import androidx.ui.input.TextInputService
import androidx.ui.painting.Canvas
import androidx.ui.text.AnnotatedString
-import androidx.ui.text.TextPainter
+import androidx.ui.text.TextDelegate
import androidx.ui.text.TextRange
import androidx.ui.text.TextStyle
import com.nhaarman.mockitokotlin2.any
@@ -52,7 +54,7 @@
class TextFieldDelegateTest {
private lateinit var canvas: Canvas
- private lateinit var painter: TextPainter
+ private lateinit var mDelegate: TextDelegate
private lateinit var processor: EditProcessor
private lateinit var onValueChange: (EditorModel) -> Unit
private lateinit var onEditorActionPerformed: (Any) -> Unit
@@ -92,7 +94,7 @@
@Before
fun setup() {
- painter = mock()
+ mDelegate = mock()
canvas = mock()
processor = mock()
onValueChange = mock()
@@ -108,18 +110,18 @@
TextFieldDelegate.draw(
canvas = canvas,
- textPainter = painter,
+ textDelegate = mDelegate,
value = EditorModel(text = "Hello, World", selection = selection),
editorStyle = EditorStyle(selectionColor = selectionColor),
hasFocus = true,
offsetMap = identityOffsetMap
)
- verify(painter, times(1)).paintBackground(
+ verify(mDelegate, times(1)).paintBackground(
eq(selection.start), eq(selection.end), eq(selectionColor), eq(canvas))
- verify(painter, times(1)).paint(eq(canvas))
+ verify(mDelegate, times(1)).paint(eq(canvas))
- verify(painter, never()).paintCursor(any(), any())
+ verify(mDelegate, never()).paintCursor(any(), any())
}
@Test
@@ -128,16 +130,16 @@
TextFieldDelegate.draw(
canvas = canvas,
- textPainter = painter,
+ textDelegate = mDelegate,
value = EditorModel(text = "Hello, World", selection = cursor),
editorStyle = EditorStyle(),
hasFocus = true,
offsetMap = identityOffsetMap
)
- verify(painter, times(1)).paintCursor(eq(cursor.start), eq(canvas))
- verify(painter, times(1)).paint(eq(canvas))
- verify(painter, never()).paintBackground(any(), any(), any(), any())
+ verify(mDelegate, times(1)).paintCursor(eq(cursor.start), eq(canvas))
+ verify(mDelegate, times(1)).paint(eq(canvas))
+ verify(mDelegate, never()).paintBackground(any(), any(), any(), any())
}
@Test
@@ -146,16 +148,16 @@
TextFieldDelegate.draw(
canvas = canvas,
- textPainter = painter,
+ textDelegate = mDelegate,
value = EditorModel(text = "Hello, World", selection = cursor),
editorStyle = EditorStyle(),
hasFocus = false,
offsetMap = identityOffsetMap
)
- verify(painter, never()).paintCursor(any(), any())
- verify(painter, times(1)).paint(eq(canvas))
- verify(painter, never()).paintBackground(any(), any(), any(), any())
+ verify(mDelegate, never()).paintCursor(any(), any())
+ verify(mDelegate, times(1)).paint(eq(canvas))
+ verify(mDelegate, never()).paintBackground(any(), any(), any(), any())
}
@Test
@@ -167,7 +169,7 @@
TextFieldDelegate.draw(
canvas = canvas,
- textPainter = painter,
+ textDelegate = mDelegate,
value = EditorModel(text = "Hello, World", selection = cursor,
composition = composition),
editorStyle = EditorStyle(compositionColor = compositionColor),
@@ -175,10 +177,10 @@
offsetMap = identityOffsetMap
)
- verify(painter, times(1)).paintBackground(
+ verify(mDelegate, times(1)).paintBackground(
eq(composition.start), eq(composition.end), eq(compositionColor), eq(canvas))
- verify(painter, times(1)).paint(eq(canvas))
- verify(painter, times(1)).paintCursor(eq(cursor.start), any())
+ verify(mDelegate, times(1)).paint(eq(canvas))
+ verify(mDelegate, times(1)).paintCursor(eq(cursor.start), any())
}
@Test
@@ -199,7 +201,7 @@
val offset = 10
val dummyEditorState = EditorModel(text = "Hello, World", selection = TextRange(1, 1))
- whenever(painter.getOffsetForPosition(position)).thenReturn(offset)
+ whenever(mDelegate.getOffsetForPosition(position)).thenReturn(offset)
val captor = argumentCaptor<List<EditOperation>>()
@@ -207,7 +209,7 @@
TextFieldDelegate.onRelease(
position,
- painter,
+ mDelegate,
processor,
identityOffsetMap,
onValueChange,
@@ -226,10 +228,10 @@
val position = PxPosition(100.px, 200.px)
val offset = 10
- whenever(painter.getOffsetForPosition(position)).thenReturn(offset)
+ whenever(mDelegate.getOffsetForPosition(position)).thenReturn(offset)
TextFieldDelegate.onRelease(
position,
- painter,
+ mDelegate,
processor,
identityOffsetMap,
onValueChange,
@@ -246,7 +248,7 @@
TextFieldDelegate.draw(
canvas = canvas,
- textPainter = painter,
+ textDelegate = mDelegate,
value = EditorModel(
text = "Hello, World", selection = TextRange(1, 1),
composition = TextRange(1, 3)
@@ -256,9 +258,9 @@
offsetMap = identityOffsetMap
)
- inOrder(painter) {
- verify(painter).paintBackground(eq(1), eq(3), eq(Color.Red), eq(canvas))
- verify(painter).paintCursor(eq(1), eq(canvas))
+ inOrder(mDelegate) {
+ verify(mDelegate).paintBackground(eq(1), eq(3), eq(Color.Red), eq(canvas))
+ verify(mDelegate).paintCursor(eq(1), eq(canvas))
}
}
@@ -293,13 +295,13 @@
@Test
fun notify_focused_rect() {
val dummyRect = Rect(0f, 1f, 2f, 3f)
- whenever(painter.getBoundingBox(any())).thenReturn(dummyRect)
+ whenever(mDelegate.getBoundingBox(any())).thenReturn(dummyRect)
val dummyPoint = PxPosition(5.px, 6.px)
whenever(layoutCoordinates.localToRoot(any())).thenReturn(dummyPoint)
val dummyEditorState = EditorModel(text = "Hello, World", selection = TextRange(1, 1))
TextFieldDelegate.notifyFocusedRect(
dummyEditorState,
- painter,
+ mDelegate,
layoutCoordinates,
textInputService,
true /* hasFocus */,
@@ -313,7 +315,7 @@
val dummyEditorState = EditorModel(text = "Hello, World", selection = TextRange(1, 1))
TextFieldDelegate.notifyFocusedRect(
dummyEditorState,
- painter,
+ mDelegate,
layoutCoordinates,
textInputService,
false /* hasFocus */,
@@ -325,13 +327,13 @@
@Test
fun notify_rect_tail() {
val dummyRect = Rect(0f, 1f, 2f, 3f)
- whenever(painter.getBoundingBox(any())).thenReturn(dummyRect)
+ whenever(mDelegate.getBoundingBox(any())).thenReturn(dummyRect)
val dummyPoint = PxPosition(5.px, 6.px)
whenever(layoutCoordinates.localToRoot(any())).thenReturn(dummyPoint)
val dummyEditorState = EditorModel(text = "Hello, World", selection = TextRange(12, 12))
TextFieldDelegate.notifyFocusedRect(
dummyEditorState,
- painter,
+ mDelegate,
layoutCoordinates,
textInputService,
true /* hasFocus */,
@@ -341,25 +343,6 @@
}
@Test
- fun notify_rect_empty() {
- val dummyHeight = 64f
- whenever(painter.preferredLineHeight).thenReturn(dummyHeight)
- val dummyPoint = PxPosition(5.px, 6.px)
- whenever(layoutCoordinates.localToRoot(any())).thenReturn(dummyPoint)
- val dummyEditorState = EditorModel(text = "", selection = TextRange(0, 0))
- TextFieldDelegate.notifyFocusedRect(
- dummyEditorState,
- painter,
- layoutCoordinates,
- textInputService,
- true, /* hasFocus */
- identityOffsetMap)
- val captor = argumentCaptor<Rect>()
- verify(textInputService).notifyFocusedRect(captor.capture())
- assertEquals(dummyHeight, captor.firstValue.height)
- }
-
- @Test
fun layout() {
val constraints = Constraints(
minWidth = 0.px.round(),
@@ -369,17 +352,20 @@
)
val dummyText = AnnotatedString(text = "Hello, World")
- whenever(painter.text).thenReturn(dummyText)
- whenever(painter.style).thenReturn(TextStyle())
- whenever(painter.density).thenReturn(Density(1.0f))
- whenever(painter.resourceLoader).thenReturn(mock())
- whenever(painter.height).thenReturn(512.0f)
+ whenever(mDelegate.text).thenReturn(dummyText)
+ whenever(mDelegate.textStyle).thenReturn(TextStyle())
+ whenever(mDelegate.density).thenReturn(Density(1.0f))
+ whenever(mDelegate.resourceLoader).thenReturn(mock())
+ whenever(mDelegate.height).thenReturn(512.0f)
- val res = TextFieldDelegate.layout(painter, constraints)
+ val res = TextFieldDelegate.layout(mDelegate, constraints)
assertEquals(1024.px.round(), res.first)
assertEquals(512.px.round(), res.second)
- verify(painter, times(1)).layout(constraints)
+ val captor = argumentCaptor<Constraints>()
+ verify(mDelegate, times(1)).layout(captor.capture())
+ assertEquals(1024.ipx, captor.firstValue.minWidth)
+ assertEquals(1024.ipx, captor.firstValue.maxWidth)
}
@Test
@@ -389,7 +375,7 @@
TextFieldDelegate.draw(
canvas = canvas,
- textPainter = painter,
+ textDelegate = mDelegate,
value = EditorModel(text = "Hello, World", selection = selection),
editorStyle = EditorStyle(selectionColor = selectionColor),
hasFocus = true,
@@ -399,7 +385,7 @@
val selectionStartInTransformedText = selection.start * 2
val selectionEmdInTransformedText = selection.end * 2
- verify(painter, times(1)).paintBackground(
+ verify(mDelegate, times(1)).paintBackground(
eq(selectionStartInTransformedText),
eq(selectionEmdInTransformedText),
eq(selectionColor),
@@ -411,18 +397,18 @@
val dummyRect = Rect(0f, 1f, 2f, 3f)
val dummyPoint = PxPosition(5.px, 6.px)
val dummyEditorState = EditorModel(text = "Hello, World", selection = TextRange(1, 3))
- whenever(painter.getBoundingBox(any())).thenReturn(dummyRect)
+ whenever(mDelegate.getBoundingBox(any())).thenReturn(dummyRect)
whenever(layoutCoordinates.localToRoot(any())).thenReturn(dummyPoint)
TextFieldDelegate.notifyFocusedRect(
dummyEditorState,
- painter,
+ mDelegate,
layoutCoordinates,
textInputService,
true /* hasFocus */,
skippingOffsetMap
)
- verify(painter).getBoundingBox(6)
+ verify(mDelegate).getBoundingBox(6)
verify(textInputService).notifyFocusedRect(any())
}
@@ -432,7 +418,7 @@
val offset = 10
val dummyEditorState = EditorModel(text = "Hello, World", selection = TextRange(1, 1))
- whenever(painter.getOffsetForPosition(position)).thenReturn(offset)
+ whenever(mDelegate.getOffsetForPosition(position)).thenReturn(offset)
val captor = argumentCaptor<List<EditOperation>>()
@@ -440,7 +426,7 @@
TextFieldDelegate.onRelease(
position,
- painter,
+ mDelegate,
processor,
skippingOffsetMap,
onValueChange,
diff --git a/ui/ui-layout/api/1.0.0-alpha01.txt b/ui/ui-layout/api/1.0.0-alpha01.txt
index 76dd60f..6a517d6 100644
--- a/ui/ui-layout/api/1.0.0-alpha01.txt
+++ b/ui/ui-layout/api/1.0.0-alpha01.txt
@@ -142,23 +142,6 @@
method public static void Padding(androidx.ui.core.Dp padding, kotlin.jvm.functions.Function0<kotlin.Unit> children);
}
- public final class ScrollerKt {
- ctor public ScrollerKt();
- method public static void HorizontalScroller(androidx.ui.layout.ScrollerPosition scrollerPosition = +memo({
- <init>()
-}), kotlin.jvm.functions.Function2<? super androidx.ui.core.Px,? super androidx.ui.core.Px,kotlin.Unit> onScrollChanged = { position, _ -> scrollerPosition.position = position }, kotlin.jvm.functions.Function0<kotlin.Unit> child);
- method public static void VerticalScroller(androidx.ui.layout.ScrollerPosition scrollerPosition = +memo({
- <init>()
-}), kotlin.jvm.functions.Function2<? super androidx.ui.core.Px,? super androidx.ui.core.Px,kotlin.Unit> onScrollChanged = { position, _ -> scrollerPosition.position = position }, kotlin.jvm.functions.Function0<kotlin.Unit> child);
- }
-
- public final class ScrollerPosition {
- ctor public ScrollerPosition();
- method public androidx.ui.core.Px getPosition();
- method public void setPosition(androidx.ui.core.Px p);
- property public final androidx.ui.core.Px position;
- }
-
public final class SpacerKt {
ctor public SpacerKt();
method public static void FixedSpacer(androidx.ui.core.Dp width, androidx.ui.core.Dp height);
diff --git a/ui/ui-layout/api/current.txt b/ui/ui-layout/api/current.txt
index 76dd60f..6a517d6 100644
--- a/ui/ui-layout/api/current.txt
+++ b/ui/ui-layout/api/current.txt
@@ -142,23 +142,6 @@
method public static void Padding(androidx.ui.core.Dp padding, kotlin.jvm.functions.Function0<kotlin.Unit> children);
}
- public final class ScrollerKt {
- ctor public ScrollerKt();
- method public static void HorizontalScroller(androidx.ui.layout.ScrollerPosition scrollerPosition = +memo({
- <init>()
-}), kotlin.jvm.functions.Function2<? super androidx.ui.core.Px,? super androidx.ui.core.Px,kotlin.Unit> onScrollChanged = { position, _ -> scrollerPosition.position = position }, kotlin.jvm.functions.Function0<kotlin.Unit> child);
- method public static void VerticalScroller(androidx.ui.layout.ScrollerPosition scrollerPosition = +memo({
- <init>()
-}), kotlin.jvm.functions.Function2<? super androidx.ui.core.Px,? super androidx.ui.core.Px,kotlin.Unit> onScrollChanged = { position, _ -> scrollerPosition.position = position }, kotlin.jvm.functions.Function0<kotlin.Unit> child);
- }
-
- public final class ScrollerPosition {
- ctor public ScrollerPosition();
- method public androidx.ui.core.Px getPosition();
- method public void setPosition(androidx.ui.core.Px p);
- property public final androidx.ui.core.Px position;
- }
-
public final class SpacerKt {
ctor public SpacerKt();
method public static void FixedSpacer(androidx.ui.core.Dp width, androidx.ui.core.Dp height);
diff --git a/ui/ui-layout/api/restricted_1.0.0-alpha01.txt b/ui/ui-layout/api/restricted_1.0.0-alpha01.txt
index 76dd60f..6a517d6 100644
--- a/ui/ui-layout/api/restricted_1.0.0-alpha01.txt
+++ b/ui/ui-layout/api/restricted_1.0.0-alpha01.txt
@@ -142,23 +142,6 @@
method public static void Padding(androidx.ui.core.Dp padding, kotlin.jvm.functions.Function0<kotlin.Unit> children);
}
- public final class ScrollerKt {
- ctor public ScrollerKt();
- method public static void HorizontalScroller(androidx.ui.layout.ScrollerPosition scrollerPosition = +memo({
- <init>()
-}), kotlin.jvm.functions.Function2<? super androidx.ui.core.Px,? super androidx.ui.core.Px,kotlin.Unit> onScrollChanged = { position, _ -> scrollerPosition.position = position }, kotlin.jvm.functions.Function0<kotlin.Unit> child);
- method public static void VerticalScroller(androidx.ui.layout.ScrollerPosition scrollerPosition = +memo({
- <init>()
-}), kotlin.jvm.functions.Function2<? super androidx.ui.core.Px,? super androidx.ui.core.Px,kotlin.Unit> onScrollChanged = { position, _ -> scrollerPosition.position = position }, kotlin.jvm.functions.Function0<kotlin.Unit> child);
- }
-
- public final class ScrollerPosition {
- ctor public ScrollerPosition();
- method public androidx.ui.core.Px getPosition();
- method public void setPosition(androidx.ui.core.Px p);
- property public final androidx.ui.core.Px position;
- }
-
public final class SpacerKt {
ctor public SpacerKt();
method public static void FixedSpacer(androidx.ui.core.Dp width, androidx.ui.core.Dp height);
diff --git a/ui/ui-layout/api/restricted_current.txt b/ui/ui-layout/api/restricted_current.txt
index 76dd60f..6a517d6 100644
--- a/ui/ui-layout/api/restricted_current.txt
+++ b/ui/ui-layout/api/restricted_current.txt
@@ -142,23 +142,6 @@
method public static void Padding(androidx.ui.core.Dp padding, kotlin.jvm.functions.Function0<kotlin.Unit> children);
}
- public final class ScrollerKt {
- ctor public ScrollerKt();
- method public static void HorizontalScroller(androidx.ui.layout.ScrollerPosition scrollerPosition = +memo({
- <init>()
-}), kotlin.jvm.functions.Function2<? super androidx.ui.core.Px,? super androidx.ui.core.Px,kotlin.Unit> onScrollChanged = { position, _ -> scrollerPosition.position = position }, kotlin.jvm.functions.Function0<kotlin.Unit> child);
- method public static void VerticalScroller(androidx.ui.layout.ScrollerPosition scrollerPosition = +memo({
- <init>()
-}), kotlin.jvm.functions.Function2<? super androidx.ui.core.Px,? super androidx.ui.core.Px,kotlin.Unit> onScrollChanged = { position, _ -> scrollerPosition.position = position }, kotlin.jvm.functions.Function0<kotlin.Unit> child);
- }
-
- public final class ScrollerPosition {
- ctor public ScrollerPosition();
- method public androidx.ui.core.Px getPosition();
- method public void setPosition(androidx.ui.core.Px p);
- property public final androidx.ui.core.Px position;
- }
-
public final class SpacerKt {
ctor public SpacerKt();
method public static void FixedSpacer(androidx.ui.core.Dp width, androidx.ui.core.Dp height);
diff --git a/ui/ui-layout/integration-tests/layout-demos/src/main/AndroidManifest.xml b/ui/ui-layout/integration-tests/layout-demos/src/main/AndroidManifest.xml
index 59cf792..0bbe95f 100644
--- a/ui/ui-layout/integration-tests/layout-demos/src/main/AndroidManifest.xml
+++ b/ui/ui-layout/integration-tests/layout-demos/src/main/AndroidManifest.xml
@@ -37,15 +37,6 @@
</intent-filter>
</activity>
- <activity android:name=".ScrollerActivity"
- android:configChanges="orientation|screenSize"
- android:label="Scroller">
- <intent-filter>
- <action android:name="android.intent.action.MAIN" />
- <category android:name="androidx.ui.demos.SAMPLE_CODE" />
- </intent-filter>
- </activity>
-
<activity android:name=".TableActivity"
android:configChanges="orientation|screenSize"
android:label="Layout/Table">
diff --git a/ui/ui-layout/integration-tests/layout-demos/src/main/java/androidx/ui/layout/demos/ComplexLayoutDemos.kt b/ui/ui-layout/integration-tests/layout-demos/src/main/java/androidx/ui/layout/demos/ComplexLayoutDemos.kt
index 1a11ec8..3e957be 100644
--- a/ui/ui-layout/integration-tests/layout-demos/src/main/java/androidx/ui/layout/demos/ComplexLayoutDemos.kt
+++ b/ui/ui-layout/integration-tests/layout-demos/src/main/java/androidx/ui/layout/demos/ComplexLayoutDemos.kt
@@ -42,7 +42,6 @@
import androidx.ui.layout.Stack
import androidx.ui.graphics.Color
import androidx.ui.painting.Paint
-import androidx.compose.Children
import androidx.compose.Composable
import androidx.compose.Model
import androidx.compose.composer
@@ -60,8 +59,8 @@
*/
@Composable
fun IntrinsicWidth(children: @Composable() () -> Unit) {
- ComplexLayout(children = children, block = {
- layout { measurables, constraints ->
+ ComplexLayout(children) {
+ measure { measurables, constraints ->
// Force child be as wide as its min intrinsic width.
val width = measurables.first().minIntrinsicWidth(constraints.minHeight)
val childConstraints = Constraints(
@@ -71,7 +70,7 @@
constraints.maxHeight
)
val childPlaceable = measurables.first().measure(childConstraints)
- layoutResult(childPlaceable.width, childPlaceable.height) {
+ layout(childPlaceable.width, childPlaceable.height) {
childPlaceable.place(IntPx.Zero, IntPx.Zero)
}
}
@@ -87,7 +86,7 @@
maxIntrinsicHeight { measurables, w ->
measurables.first().maxIntrinsicHeight(w)
}
- })
+ }
}
/**
@@ -96,8 +95,8 @@
@Composable
fun RectangleWithIntrinsics(color: Color) {
ComplexLayout(children = { DrawRectangle(color = color) }, block = {
- layout { _, _ ->
- layoutResult(80.ipx, 80.ipx) {}
+ measure { _, _ ->
+ layout(80.ipx, 80.ipx) {}
}
minIntrinsicWidth { _, _ -> 30.ipx }
maxIntrinsicWidth { _, _ -> 150.ipx }
@@ -228,7 +227,7 @@
@Composable
fun SingleCompositionRow(children: @Composable() () -> Unit) {
- Layout(layoutBlock = { measurables, constraints ->
+ Layout(children) { measurables, constraints ->
val placeables = measurables.map {
it.measure(constraints.copy(minWidth = 0.ipx, maxWidth = IntPx.Infinity))
}
@@ -242,12 +241,12 @@
left += placeable.width
}
}
- }, children = children)
+ }
}
@Composable
fun SingleCompositionColumn(children: @Composable() () -> Unit) {
- Layout(layoutBlock = { measurables, constraints ->
+ Layout(children) { measurables, constraints ->
val placeables = measurables.map {
it.measure(constraints.copy(minHeight = 0.ipx, maxHeight = IntPx.Infinity))
}
@@ -261,7 +260,7 @@
top += placeable.height
}
}
- }, children = children)
+ }
}
@Composable
@@ -272,13 +271,10 @@
canvas.drawRect(parentSize.toRect(), paint)
}
}
- Layout(
- layoutBlock = { _, constraints ->
- val size = constraints.constrain(IntPxSize(30.ipx, 30.ipx))
- layout(size.width, size.height) { }
- },
- children = Rectangle
- )
+ Layout(Rectangle) { _, constraints ->
+ val size = constraints.constrain(IntPxSize(30.ipx, 30.ipx))
+ layout(size.width, size.height) { }
+ }
}
@Model
@@ -295,8 +291,8 @@
}
}
ComplexLayout(children = Rectangle, block = {
- layout { _, _ ->
- layoutResult(50.ipx, 50.ipx) {}
+ measure { _, _ ->
+ layout(50.ipx, 50.ipx) {}
}
minIntrinsicWidth { _, _ -> 50.ipx }
maxIntrinsicWidth { _, _ -> 50.ipx }
@@ -308,7 +304,7 @@
@Composable
fun SingleCompositionRowWithIntrinsics(children: @Composable() () -> Unit) {
ComplexLayout(children = children, block = {
- layout { measurables, constraints ->
+ measure { measurables, constraints ->
val placeables = measurables.map { measurable ->
val childWidth = measurable.maxIntrinsicWidth(constraints.maxHeight)
measurable.measure(
@@ -320,7 +316,7 @@
val width = placeables.map { it.width }.sum()
val height = placeables.map { it.height }.max()
- layoutResult(width, height) {
+ layout(width, height) {
var left = 0.ipx
placeables.forEach { placeable ->
placeable.place(left, 0.ipx)
diff --git a/ui/ui-layout/integration-tests/layout-demos/src/main/java/androidx/ui/layout/demos/ScrollerActivity.kt b/ui/ui-layout/integration-tests/layout-demos/src/main/java/androidx/ui/layout/demos/ScrollerActivity.kt
deleted file mode 100644
index e59ce03..0000000
--- a/ui/ui-layout/integration-tests/layout-demos/src/main/java/androidx/ui/layout/demos/ScrollerActivity.kt
+++ /dev/null
@@ -1,84 +0,0 @@
-/*
- * 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.layout.demos
-
-import android.app.Activity
-import android.os.Bundle
-import androidx.ui.core.Density
-import androidx.ui.core.Text
-import androidx.ui.core.dp
-import androidx.ui.core.withDensity
-import androidx.ui.layout.Column
-import androidx.ui.layout.Padding
-import androidx.ui.layout.VerticalScroller
-import androidx.compose.composer
-import androidx.ui.text.TextStyle
-import androidx.ui.core.setContent
-import androidx.ui.core.sp
-
-class ScrollerActivity : Activity() {
- private val phrases = listOf(
- "Easy As Pie",
- "Wouldn't Harm a Fly",
- "No-Brainer",
- "Keep On Truckin'",
- "An Arm and a Leg",
- "Down To Earth",
- "Under the Weather",
- "Up In Arms",
- "Cup Of Joe",
- "Not the Sharpest Tool in the Shed",
- "Ring Any Bells?",
- "Son of a Gun",
- "Hard Pill to Swallow",
- "Close But No Cigar",
- "Beating a Dead Horse",
- "If You Can't Stand the Heat, Get Out of the Kitchen",
- "Cut To The Chase",
- "Heads Up",
- "Goody Two-Shoes",
- "Fish Out Of Water",
- "Cry Over Spilt Milk",
- "Elephant in the Room",
- "There's No I in Team",
- "Poke Fun At",
- "Talk the Talk",
- "Know the Ropes",
- "Fool's Gold",
- "It's Not Brain Surgery",
- "Fight Fire With Fire",
- "Go For Broke"
- )
-
- override fun onCreate(savedInstanceState: Bundle?) {
- super.onCreate(savedInstanceState)
- val density = Density(this)
- withDensity(density) {
- val style = TextStyle(fontSize = 30.sp)
- setContent {
- Padding(padding = 10.dp) {
- VerticalScroller {
- Column {
- phrases.forEach { phrase ->
- Text(text = phrase, style = style)
- }
- }
- }
- }
- }
- }
- }
-}
diff --git a/ui/ui-layout/integration-tests/samples/src/main/java/androidx/ui/layout/samples/StackSamples.kt b/ui/ui-layout/integration-tests/samples/src/main/java/androidx/ui/layout/samples/StackSamples.kt
index e816b27..23346d3 100644
--- a/ui/ui-layout/integration-tests/samples/src/main/java/androidx/ui/layout/samples/StackSamples.kt
+++ b/ui/ui-layout/integration-tests/samples/src/main/java/androidx/ui/layout/samples/StackSamples.kt
@@ -35,13 +35,11 @@
*/
@Composable
fun SizedRectangle(color: Color, width: Dp? = null, height: Dp? = null) {
- Layout(
- children = { DrawRectangle(color = color) },
- layoutBlock = { _, constraints ->
- val widthPx = width?.toIntPx() ?: constraints.maxWidth
- val heightPx = height?.toIntPx() ?: constraints.maxHeight
- layout(widthPx, heightPx) {}
- })
+ Layout(children = { DrawRectangle(color = color) }) { _, constraints ->
+ val widthPx = width?.toIntPx() ?: constraints.maxWidth
+ val heightPx = height?.toIntPx() ?: constraints.maxHeight
+ layout(widthPx, heightPx) {}
+ }
}
@Composable
diff --git a/ui/ui-layout/src/androidTest/java/androidx/ui/layout/test/AlignTest.kt b/ui/ui-layout/src/androidTest/java/androidx/ui/layout/test/AlignTest.kt
index d4cab2b..64fd740 100644
--- a/ui/ui-layout/src/androidTest/java/androidx/ui/layout/test/AlignTest.kt
+++ b/ui/ui-layout/src/androidTest/java/androidx/ui/layout/test/AlignTest.kt
@@ -113,7 +113,7 @@
}
}
},
- layoutBlock = { measurables, constraints ->
+ measureBlock = { measurables, constraints ->
val placeable = measurables.first().measure(Constraints())
layout(constraints.maxWidth, constraints.maxHeight) {
placeable.place(0.ipx, 0.ipx)
@@ -236,7 +236,7 @@
}
}
}
- }, layoutBlock = { measurables, constraints ->
+ }, measureBlock = { measurables, constraints ->
val placeable = measurables.first().measure(Constraints())
layout(constraints.maxWidth, constraints.maxHeight) {
placeable.place(0.ipx, 0.ipx)
diff --git a/ui/ui-layout/src/androidTest/java/androidx/ui/layout/test/ContainerTest.kt b/ui/ui-layout/src/androidTest/java/androidx/ui/layout/test/ContainerTest.kt
index 8a68a98..6ffd748 100644
--- a/ui/ui-layout/src/androidTest/java/androidx/ui/layout/test/ContainerTest.kt
+++ b/ui/ui-layout/src/androidTest/java/androidx/ui/layout/test/ContainerTest.kt
@@ -352,11 +352,11 @@
@Composable
fun EmptyBox(width: Dp, height: Dp) {
- Layout(layoutBlock = { _, constraints ->
+ Layout(children = { }) { _, constraints ->
layout(
width.toIntPx().coerceIn(constraints.minWidth, constraints.maxWidth),
height.toIntPx().coerceIn(constraints.minHeight, constraints.maxHeight)
) {}
- }, children = { })
+ }
}
}
diff --git a/ui/ui-layout/src/androidTest/java/androidx/ui/layout/test/IntrinsicTest.kt b/ui/ui-layout/src/androidTest/java/androidx/ui/layout/test/IntrinsicTest.kt
index 556d6b4..6c62f1e 100644
--- a/ui/ui-layout/src/androidTest/java/androidx/ui/layout/test/IntrinsicTest.kt
+++ b/ui/ui-layout/src/androidTest/java/androidx/ui/layout/test/IntrinsicTest.kt
@@ -16,7 +16,6 @@
package androidx.ui.layout.test
-import androidx.compose.Children
import androidx.compose.Composable
import androidx.compose.composer
import androidx.test.filters.SmallTest
@@ -499,12 +498,13 @@
width: Dp,
maxIntrinsicWidth: Dp,
minIntrinsicHeight: Dp,
- height: Dp, maxIntrinsicHeight: Dp,
+ height: Dp,
+ maxIntrinsicHeight: Dp,
children: @Composable() () -> Unit
) {
ComplexLayout(children) {
- layout { _, constraints ->
- layoutResult(
+ measure { _, constraints ->
+ layout(
width.toIntPx().coerceIn(constraints.minWidth, constraints.maxWidth),
height.toIntPx().coerceIn(constraints.minHeight, constraints.maxHeight)
) { }
diff --git a/ui/ui-layout/src/androidTest/java/androidx/ui/layout/test/LayoutTest.kt b/ui/ui-layout/src/androidTest/java/androidx/ui/layout/test/LayoutTest.kt
index d8c6eb1..6b940fb 100644
--- a/ui/ui-layout/src/androidTest/java/androidx/ui/layout/test/LayoutTest.kt
+++ b/ui/ui-layout/src/androidTest/java/androidx/ui/layout/test/LayoutTest.kt
@@ -29,7 +29,6 @@
import androidx.ui.core.PxSize
import androidx.ui.core.Ref
import androidx.ui.core.px
-import androidx.compose.Children
import androidx.compose.Composable
import androidx.compose.composer
import androidx.ui.core.ComplexLayout
@@ -140,7 +139,7 @@
val layoutLatch = CountDownLatch(1)
show {
ComplexLayout(layout) {
- layout { measurables, _ ->
+ measure { measurables, _ ->
val measurable = measurables.first()
test(
{ h -> measurable.minIntrinsicWidth(h) },
@@ -149,7 +148,7 @@
{ w -> measurable.maxIntrinsicHeight(w) }
)
layoutLatch.countDown()
- layoutResult(0.ipx, 0.ipx) {}
+ layout(0.ipx, 0.ipx) {}
}
minIntrinsicWidth { _, _ -> 0.ipx }
maxIntrinsicWidth { _, _ -> 0.ipx }
diff --git a/ui/ui-layout/src/main/java/androidx/ui/layout/Align.kt b/ui/ui-layout/src/main/java/androidx/ui/layout/Align.kt
index 888706f..3eca354 100644
--- a/ui/ui-layout/src/main/java/androidx/ui/layout/Align.kt
+++ b/ui/ui-layout/src/main/java/androidx/ui/layout/Align.kt
@@ -21,7 +21,6 @@
import androidx.ui.core.Layout
import androidx.ui.core.isFinite
import androidx.ui.core.looseMin
-import androidx.compose.Children
import androidx.compose.Composable
import androidx.compose.composer
import androidx.ui.core.round
@@ -70,7 +69,7 @@
*/
@Composable
fun Align(alignment: Alignment, children: @Composable() () -> Unit) {
- Layout(layoutBlock = { measurables, constraints ->
+ Layout(children) { measurables, constraints ->
val measurable = measurables.firstOrNull()
// The child cannot be larger than our max constraints, but we ignore min constraints.
val placeable = measurable?.measure(constraints.looseMin())
@@ -96,7 +95,7 @@
placeable.place(position.x, position.y)
}
}
- }, children = children)
+ }
}
/**
diff --git a/ui/ui-layout/src/main/java/androidx/ui/layout/ConstrainedBox.kt b/ui/ui-layout/src/main/java/androidx/ui/layout/ConstrainedBox.kt
index 7ad2633..ed2d6f1 100644
--- a/ui/ui-layout/src/main/java/androidx/ui/layout/ConstrainedBox.kt
+++ b/ui/ui-layout/src/main/java/androidx/ui/layout/ConstrainedBox.kt
@@ -16,7 +16,6 @@
package androidx.ui.layout
-import androidx.compose.Children
import androidx.compose.Composable
import androidx.compose.composer
import androidx.ui.core.Constraints
@@ -41,14 +40,14 @@
children: @Composable() () -> Unit
) {
ComplexLayout(children) {
- layout { measurables, incomingConstraints ->
+ measure { measurables, incomingConstraints ->
val measurable = measurables.firstOrNull()
val childConstraints = Constraints(constraints).enforce(incomingConstraints)
val placeable = measurable?.measure(childConstraints)
val layoutWidth = placeable?.width ?: childConstraints.minWidth
val layoutHeight = placeable?.height ?: childConstraints.minHeight
- layoutResult(layoutWidth, layoutHeight) {
+ layout(layoutWidth, layoutHeight) {
placeable?.place(IntPx.Zero, IntPx.Zero)
}
}
diff --git a/ui/ui-layout/src/main/java/androidx/ui/layout/Container.kt b/ui/ui-layout/src/main/java/androidx/ui/layout/Container.kt
index dc48a4b..8123f2c 100644
--- a/ui/ui-layout/src/main/java/androidx/ui/layout/Container.kt
+++ b/ui/ui-layout/src/main/java/androidx/ui/layout/Container.kt
@@ -28,7 +28,6 @@
import androidx.ui.core.max
import androidx.ui.core.offset
import androidx.ui.core.withTight
-import androidx.compose.Children
import androidx.compose.Composable
import androidx.compose.composer
import androidx.compose.trace
@@ -64,7 +63,7 @@
children: @Composable() () -> Unit
) {
trace("UI:Container") {
- Layout(children = children, layoutBlock = { measurables, incomingConstraints ->
+ Layout(children) { measurables, incomingConstraints ->
val containerConstraints = Constraints(constraints)
.withTight(width?.toIntPx(), height?.toIntPx())
.enforce(incomingConstraints)
@@ -98,6 +97,6 @@
)
}
}
- })
+ }
}
}
diff --git a/ui/ui-layout/src/main/java/androidx/ui/layout/Flex.kt b/ui/ui-layout/src/main/java/androidx/ui/layout/Flex.kt
index 2693e86..9321ebe 100644
--- a/ui/ui-layout/src/main/java/androidx/ui/layout/Flex.kt
+++ b/ui/ui-layout/src/main/java/androidx/ui/layout/Flex.kt
@@ -17,7 +17,6 @@
package androidx.ui.layout
import androidx.annotation.FloatRange
-import androidx.compose.Children
import androidx.compose.Composable
import androidx.compose.composer
import androidx.ui.core.Constraints
@@ -489,7 +488,7 @@
composable
}
ComplexLayout(flexChildren) {
- layout { children, outerConstraints ->
+ measure { children, outerConstraints ->
val constraints = OrientationIndependentConstraints(outerConstraints, orientation)
var totalFlex = 0f
@@ -585,7 +584,7 @@
} else {
mainAxisLayoutSize
}
- layoutResult(layoutWidth, layoutHeight) {
+ layout(layoutWidth, layoutHeight) {
val childrenMainAxisSize = placeables.map { it!!.mainAxisSize() }
val mainAxisPositions = mainAxisAlignment.aligner
.align(mainAxisLayoutSize, childrenMainAxisSize)
diff --git a/ui/ui-layout/src/main/java/androidx/ui/layout/Intrinsic.kt b/ui/ui-layout/src/main/java/androidx/ui/layout/Intrinsic.kt
index 3bd2915..a9d3480 100644
--- a/ui/ui-layout/src/main/java/androidx/ui/layout/Intrinsic.kt
+++ b/ui/ui-layout/src/main/java/androidx/ui/layout/Intrinsic.kt
@@ -16,7 +16,6 @@
package androidx.ui.layout
-import androidx.compose.Children
import androidx.compose.Composable
import androidx.compose.composer
import androidx.ui.core.ComplexLayout
@@ -44,13 +43,13 @@
@Composable
fun MinIntrinsicWidth(children: @Composable() () -> Unit) {
ComplexLayout(children) {
- layout { measurables, constraints ->
+ measure { measurables, constraints ->
val measurable = measurables.firstOrNull()
val width = measurable?.minIntrinsicWidth(constraints.maxHeight) ?: 0.ipx
val placeable = measurable?.measure(
Constraints.tightConstraintsForWidth(width).enforce(constraints)
)
- layoutResult(placeable?.width ?: 0.ipx, placeable?.height ?: 0.ipx) {
+ layout(placeable?.width ?: 0.ipx, placeable?.height ?: 0.ipx) {
placeable?.place(0.ipx, 0.ipx)
}
}
@@ -89,13 +88,13 @@
@Composable
fun MinIntrinsicHeight(children: @Composable() () -> Unit) {
ComplexLayout(children) {
- layout { measurables, constraints ->
+ measure { measurables, constraints ->
val measurable = measurables.firstOrNull()
val height = measurable?.minIntrinsicHeight(constraints.maxWidth) ?: 0.ipx
val placeable = measurable?.measure(
Constraints.tightConstraintsForHeight(height).enforce(constraints)
)
- layoutResult(placeable?.width ?: 0.ipx, placeable?.height ?: 0.ipx) {
+ layout(placeable?.width ?: 0.ipx, placeable?.height ?: 0.ipx) {
placeable?.place(0.ipx, 0.ipx)
}
}
@@ -134,13 +133,13 @@
@Composable
fun MaxIntrinsicWidth(children: @Composable() () -> Unit) {
ComplexLayout(children) {
- layout { measurables, constraints ->
+ measure { measurables, constraints ->
val measurable = measurables.firstOrNull()
val width = measurable?.maxIntrinsicWidth(constraints.maxHeight) ?: 0.ipx
val placeable = measurable?.measure(
Constraints.tightConstraintsForWidth(width).enforce(constraints)
)
- layoutResult(placeable?.width ?: 0.ipx, placeable?.height ?: 0.ipx) {
+ layout(placeable?.width ?: 0.ipx, placeable?.height ?: 0.ipx) {
placeable?.place(0.ipx, 0.ipx)
}
}
@@ -179,13 +178,13 @@
@Composable
fun MaxIntrinsicHeight(children: @Composable() () -> Unit) {
ComplexLayout(children) {
- layout { measurables, constraints ->
+ measure { measurables, constraints ->
val measurable = measurables.firstOrNull()
val height = measurable?.maxIntrinsicHeight(constraints.maxWidth) ?: 0.ipx
val placeable = measurable?.measure(
Constraints.tightConstraintsForHeight(height).enforce(constraints)
)
- layoutResult(placeable?.width ?: 0.ipx, placeable?.height ?: 0.ipx) {
+ layout(placeable?.width ?: 0.ipx, placeable?.height ?: 0.ipx) {
placeable?.place(0.ipx, 0.ipx)
}
}
diff --git a/ui/ui-layout/src/main/java/androidx/ui/layout/Padding.kt b/ui/ui-layout/src/main/java/androidx/ui/layout/Padding.kt
index 79225a6..805608f 100644
--- a/ui/ui-layout/src/main/java/androidx/ui/layout/Padding.kt
+++ b/ui/ui-layout/src/main/java/androidx/ui/layout/Padding.kt
@@ -21,7 +21,6 @@
import androidx.ui.core.dp
import androidx.ui.core.min
import androidx.ui.core.offset
-import androidx.compose.Children
import androidx.compose.Composable
import androidx.compose.composer
@@ -55,7 +54,7 @@
padding: EdgeInsets,
children: @Composable() () -> Unit
) {
- Layout(layoutBlock = { measurables, constraints ->
+ Layout(children) { measurables, constraints ->
val measurable = measurables.firstOrNull()
if (measurable == null) {
layout(constraints.minWidth, constraints.minHeight) { }
@@ -78,7 +77,7 @@
placeable.place(paddingLeft, paddingTop)
}
}
- }, children = children)
+ }
}
/**
diff --git a/ui/ui-layout/src/main/java/androidx/ui/layout/Scroller.kt b/ui/ui-layout/src/main/java/androidx/ui/layout/Scroller.kt
deleted file mode 100644
index 7686861..0000000
--- a/ui/ui-layout/src/main/java/androidx/ui/layout/Scroller.kt
+++ /dev/null
@@ -1,214 +0,0 @@
-/*
- * 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.layout
-
-import androidx.ui.core.coerceIn
-import androidx.ui.core.Constraints
-import androidx.ui.core.Direction
-import androidx.ui.core.IntPx
-import androidx.ui.core.Layout
-import androidx.ui.core.Px
-import androidx.ui.core.PxPosition
-import androidx.ui.core.gesture.TouchSlopDragGestureDetector
-import androidx.ui.core.gesture.DragObserver
-import androidx.ui.core.min
-import androidx.ui.core.px
-import androidx.ui.core.round
-import androidx.ui.core.toPx
-import androidx.ui.core.toRect
-import androidx.compose.Composable
-import androidx.compose.Model
-import androidx.compose.composer
-import androidx.compose.memo
-import androidx.compose.state
-import androidx.compose.unaryPlus
-import androidx.ui.core.Clip
-import androidx.ui.core.Density
-import androidx.ui.core.PxSize
-import androidx.ui.core.RepaintBoundary
-import androidx.ui.engine.geometry.Outline
-import androidx.ui.engine.geometry.Shape
-
-/**
- * Tracks the vertical drag gesture offset, allowing a range between `0.px` and [max].
- * When the offset changes, [offsetChange] is called with the new offset.
- */
-@Composable
-private fun DirectionalDragGestureDetector(
- vertical: Boolean,
- max: Px = Px.Infinity,
- offsetChange: (Px) -> Unit,
- children: @Composable() () -> Unit
-) {
- val offset = +state { 0.px }
- TouchSlopDragGestureDetector(
- dragObserver = object : DragObserver {
- override fun onDrag(dragDistance: PxPosition): PxPosition {
- val draggedAmount = if (vertical) dragDistance.y else dragDistance.x
- val dragPosition = -offset.value + draggedAmount
- val targetPosition = dragPosition.coerceIn(-max, 0.px)
- if (targetPosition != -offset.value) {
- offset.value = -targetPosition
- offsetChange(offset.value)
- }
- val consumed = draggedAmount + (targetPosition - dragPosition)
- return if (vertical) PxPosition(0.px, consumed) else PxPosition(consumed, 0.px)
- }
- },
- canDrag = { direction ->
- when (direction) {
- Direction.DOWN -> if (vertical) offset.value > 0.px else false
- Direction.UP -> if (vertical) offset.value < max else false
- Direction.RIGHT -> if (vertical) false else offset.value > 0.px
- Direction.LEFT -> if (vertical) false else offset.value < max
- }
- }) { children() }
-}
-
-/**
- * This is the state of a [VerticalScroller] that allows the developer to change the scroll
- * position. [position] must be between `0` and `maxPosition` in `onScrollChanged`'s `maxPosition`
- * parameter.
- */
-@Model
-class ScrollerPosition {
- /**
- * The amount of scrolling, between `0` and `maxPosition` in `onScrollChanged`'s `maxPosition`
- * parameter.
- */
- var position: Px = 0.px
-}
-
-/**
- * A container that composes all of its contents and lays it out, fitting the width of the child.
- * If the child's height is less than the [Constraints.maxHeight], the child's height is used,
- * or the [Constraints.maxHeight] otherwise. If the contents don't fit the height, the drag gesture
- * allows scrolling its content vertically. The contents of the VerticalScroller are clipped to
- * the VerticalScroller's bounds.
- */
-// TODO(mount): Add fling support
-@Composable
-fun VerticalScroller(
- scrollerPosition: ScrollerPosition = +memo { ScrollerPosition() },
- onScrollChanged: (position: Px, maxPosition: Px) -> Unit = { position, _ ->
- scrollerPosition.position = position
- },
- child: @Composable() () -> Unit
-) {
- Scroller(
- vertical = true,
- scrollerPosition = scrollerPosition,
- onScrollChanged = onScrollChanged,
- child = child
- )
-}
-
-/**
- * A container that composes all of its contents and lays it out, fitting the height of the child.
- * If the child's width is less than the [Constraints.maxWidth], the child's width is used,
- * or the [Constraints.maxWidth] otherwise. If the contents don't fit the width, the drag gesture
- * allows scrolling its content horizontally. The contents of the HorizontalScroller are clipped to
- * the HorizontalScroller's bounds.
- */
-// TODO(mount): Add fling support
-@Composable
-fun HorizontalScroller(
- scrollerPosition: ScrollerPosition = +memo { ScrollerPosition() },
- onScrollChanged: (position: Px, maxPosition: Px) -> Unit = { position, _ ->
- scrollerPosition.position = position
- },
- child: @Composable() () -> Unit
-) {
- Scroller(
- vertical = false,
- scrollerPosition = scrollerPosition,
- onScrollChanged = onScrollChanged,
- child = child
- )
-}
-
-@Composable
-private fun Scroller(
- vertical: Boolean,
- scrollerPosition: ScrollerPosition = +memo { ScrollerPosition() },
- onScrollChanged: (position: Px, maxPosition: Px) -> Unit = { position, _ ->
- scrollerPosition.position = position
- },
- child: @Composable() () -> Unit
-) {
- val maxPosition = +state { 0.px }
- Layout(children = {
- Clip(RectangleShape) {
- DirectionalDragGestureDetector(
- vertical = vertical,
- max = maxPosition.value,
- offsetChange = { newOffset -> onScrollChanged(newOffset, maxPosition.value) }) {
- Container {
- RepaintBoundary {
- child()
- }
- }
- }
- }
- }) { measurables, constraints ->
- val childConstraints = if (vertical) {
- constraints.copy(maxHeight = IntPx.Infinity)
- } else {
- constraints.copy(maxWidth = IntPx.Infinity)
- }
- val childMeasurable = measurables.firstOrNull()
- val placeable = childMeasurable?.measure(childConstraints)
- val width: IntPx
- val height: IntPx
- if (placeable == null) {
- width = constraints.minWidth
- height = constraints.minHeight
- } else {
- width = min(placeable.width, constraints.maxWidth)
- height = min(placeable.height, constraints.maxHeight)
- }
- layout(width, height) {
- if (placeable != null) {
- val childSize = if (vertical) {
- placeable.height.toPx()
- } else {
- placeable.width.toPx()
- }
- val newMaxPosition = childSize - if (vertical) height.toPx() else width.toPx()
- if (maxPosition.value != newMaxPosition) {
- maxPosition.value = newMaxPosition
- onScrollChanged(scrollerPosition.position, maxPosition.value)
- }
- val x: IntPx
- val y: IntPx
- if (vertical) {
- x = IntPx.Zero
- y = -scrollerPosition.position.round()
- } else {
- x = -scrollerPosition.position.round()
- y = IntPx.Zero
- }
- placeable.place(x, y)
- }
- }
- }
-}
-
-// TODO(andreykulikov): make RectangleShape from ui-foundation accessible to this class
-private val RectangleShape: Shape = object : Shape {
- override fun createOutline(size: PxSize, density: Density) =
- Outline.Rectangle(size.toRect())
-}
diff --git a/ui/ui-layout/src/main/java/androidx/ui/layout/Stack.kt b/ui/ui-layout/src/main/java/androidx/ui/layout/Stack.kt
index f852203..ef1b449 100644
--- a/ui/ui-layout/src/main/java/androidx/ui/layout/Stack.kt
+++ b/ui/ui-layout/src/main/java/androidx/ui/layout/Stack.kt
@@ -26,7 +26,6 @@
import androidx.ui.core.Placeable
import androidx.ui.core.looseMin
import androidx.ui.core.max
-import androidx.compose.Children
import androidx.compose.Composable
import androidx.compose.composer
import androidx.ui.core.coerceAtLeast
@@ -112,7 +111,7 @@
}
composable
}
- Layout(children = children, layoutBlock = { measurables, constraints ->
+ Layout(children) { measurables, constraints ->
val placeables = arrayOfNulls<Placeable>(measurables.size)
// First measure aligned children to get the size of the layout.
(0 until measurables.size).filter { i -> !measurables[i].positioned }.forEach { i ->
@@ -205,7 +204,7 @@
}
}
}
- })
+ }
}
/**
diff --git a/ui/ui-layout/src/main/java/androidx/ui/layout/Table.kt b/ui/ui-layout/src/main/java/androidx/ui/layout/Table.kt
index 36bc3b2..29eb921 100644
--- a/ui/ui-layout/src/main/java/androidx/ui/layout/Table.kt
+++ b/ui/ui-layout/src/main/java/androidx/ui/layout/Table.kt
@@ -17,7 +17,6 @@
package androidx.ui.layout
import androidx.annotation.FloatRange
-import androidx.compose.Children
import androidx.compose.Composable
import androidx.compose.composer
import androidx.compose.state
diff --git a/ui/ui-layout/src/main/java/androidx/ui/layout/Wrap.kt b/ui/ui-layout/src/main/java/androidx/ui/layout/Wrap.kt
index 3846114..51580b3 100644
--- a/ui/ui-layout/src/main/java/androidx/ui/layout/Wrap.kt
+++ b/ui/ui-layout/src/main/java/androidx/ui/layout/Wrap.kt
@@ -21,7 +21,6 @@
import androidx.ui.core.ipx
import androidx.ui.core.looseMin
import androidx.ui.core.max
-import androidx.compose.Children
import androidx.compose.Composable
import androidx.compose.composer
@@ -36,7 +35,7 @@
*/
@Composable
fun Wrap(alignment: Alignment = Alignment.TopLeft, children: @Composable() () -> Unit) {
- Layout(layoutBlock = { measurables, constraints ->
+ Layout(children) { measurables, constraints ->
val measurable = measurables.firstOrNull()
// The child cannot be larger than our max constraints, but we ignore min constraints.
val placeable = measurable?.measure(constraints.looseMin())
@@ -53,5 +52,5 @@
placeable.place(position.x, position.y)
}
}
- }, children=children)
+ }
}
diff --git a/ui/ui-material/api/1.0.0-alpha01.txt b/ui/ui-material/api/1.0.0-alpha01.txt
index 61a9c3f..f062c9e 100644
--- a/ui/ui-material/api/1.0.0-alpha01.txt
+++ b/ui/ui-material/api/1.0.0-alpha01.txt
@@ -45,7 +45,7 @@
primary
}), androidx.ui.engine.geometry.Shape shape = +themeShape({
button
-}), androidx.ui.core.Dp elevation = 0.dp);
+}), androidx.ui.core.Dp elevation = 2.dp);
method public static androidx.ui.material.ButtonStyle OutlinedButtonStyle(androidx.ui.foundation.shape.border.Border border = Border(+themeColor({
onSurface.copy(OutlinedStrokeOpacity)
}), 1.dp), androidx.ui.graphics.Color color = +themeColor({
@@ -108,13 +108,13 @@
ctor public FloatingActionButtonKt();
method public static void FloatingActionButton(kotlin.jvm.functions.Function0<kotlin.Unit>? onClick = null, androidx.ui.core.Dp minSize = FabSize, androidx.ui.engine.geometry.Shape shape = CircleShape, androidx.ui.graphics.Color color = +themeColor({
primary
-}), androidx.ui.core.Dp elevation = 0.dp, kotlin.jvm.functions.Function0<kotlin.Unit> children);
+}), androidx.ui.core.Dp elevation = 6.dp, kotlin.jvm.functions.Function0<kotlin.Unit> children);
method public static void FloatingActionButton(androidx.ui.painting.Image icon, kotlin.jvm.functions.Function0<kotlin.Unit>? onClick = null, androidx.ui.graphics.Color color = +themeColor({
primary
-}), androidx.ui.core.Dp elevation = 0.dp);
+}), androidx.ui.core.Dp elevation = 6.dp);
method public static void FloatingActionButton(String text, androidx.ui.painting.Image? icon = null, androidx.ui.text.TextStyle? textStyle = null, kotlin.jvm.functions.Function0<kotlin.Unit>? onClick = null, androidx.ui.graphics.Color color = +themeColor({
primary
-}), androidx.ui.core.Dp elevation = 0.dp);
+}), androidx.ui.core.Dp elevation = 6.dp);
method public static androidx.ui.core.Dp getExtendedFabHeight();
method public static androidx.ui.core.Dp getExtendedFabIconPadding();
method public static androidx.ui.core.Dp getExtendedFabTextPadding();
@@ -154,7 +154,6 @@
public final class MaterialThemeKt {
ctor public MaterialThemeKt();
method public static void MaterialButtonShapeTheme(kotlin.jvm.functions.Function0<kotlin.Unit> children);
- method public static void MaterialRippleTheme(kotlin.jvm.functions.Function0<kotlin.Unit> children);
method public static void MaterialTheme(androidx.ui.material.MaterialColors colors = androidx.ui.material.MaterialColors(), androidx.ui.material.MaterialTypography typography = androidx.ui.material.MaterialTypography(), kotlin.jvm.functions.Function0<kotlin.Unit> children);
method public static androidx.compose.Ambient<androidx.ui.material.MaterialColors> getColors();
method public static androidx.compose.Ambient<androidx.ui.material.Shapes> getCurrentShapeAmbient();
@@ -349,11 +348,28 @@
package androidx.ui.material.shape {
public final class CutCornerShape extends androidx.ui.foundation.shape.corner.CornerBasedShape {
- ctor public CutCornerShape(androidx.ui.foundation.shape.corner.CornerSizes corners);
- method public androidx.ui.foundation.shape.corner.CornerSizes component1();
- method public androidx.ui.material.shape.CutCornerShape copy(androidx.ui.foundation.shape.corner.CornerSizes corners);
- method public androidx.ui.engine.geometry.Outline.Generic createOutline(androidx.ui.foundation.shape.corner.PxCornerSizes corners, androidx.ui.core.PxSize size);
- method public androidx.ui.foundation.shape.corner.CornerSizes getCorners();
+ ctor public CutCornerShape(androidx.ui.foundation.shape.corner.CornerSize topLeft, androidx.ui.foundation.shape.corner.CornerSize topRight, androidx.ui.foundation.shape.corner.CornerSize bottomRight, androidx.ui.foundation.shape.corner.CornerSize bottomLeft);
+ method public androidx.ui.foundation.shape.corner.CornerSize component1();
+ method public androidx.ui.foundation.shape.corner.CornerSize component2();
+ method public androidx.ui.foundation.shape.corner.CornerSize component3();
+ method public androidx.ui.foundation.shape.corner.CornerSize component4();
+ method public androidx.ui.material.shape.CutCornerShape copy(androidx.ui.foundation.shape.corner.CornerSize topLeft, androidx.ui.foundation.shape.corner.CornerSize topRight, androidx.ui.foundation.shape.corner.CornerSize bottomRight, androidx.ui.foundation.shape.corner.CornerSize bottomLeft);
+ method public androidx.ui.engine.geometry.Outline.Generic createOutline(androidx.ui.core.PxSize size, androidx.ui.core.Px topLeft, androidx.ui.core.Px topRight, androidx.ui.core.Px bottomRight, androidx.ui.core.Px bottomLeft);
+ method public androidx.ui.foundation.shape.corner.CornerSize getBottomLeft();
+ method public androidx.ui.foundation.shape.corner.CornerSize getBottomRight();
+ method public androidx.ui.foundation.shape.corner.CornerSize getTopLeft();
+ method public androidx.ui.foundation.shape.corner.CornerSize getTopRight();
+ }
+
+ public final class CutCornerShapeKt {
+ ctor public CutCornerShapeKt();
+ method public static androidx.ui.material.shape.CutCornerShape CutCornerShape(androidx.ui.foundation.shape.corner.CornerSize corner);
+ method public static androidx.ui.material.shape.CutCornerShape CutCornerShape(androidx.ui.core.Dp size);
+ method public static androidx.ui.material.shape.CutCornerShape CutCornerShape(androidx.ui.core.Px size);
+ method public static androidx.ui.material.shape.CutCornerShape CutCornerShape(int percent);
+ method public static androidx.ui.material.shape.CutCornerShape CutCornerShape(androidx.ui.core.Dp topLeft = 0.dp, androidx.ui.core.Dp topRight = 0.dp, androidx.ui.core.Dp bottomRight = 0.dp, androidx.ui.core.Dp bottomLeft = 0.dp);
+ method public static androidx.ui.material.shape.CutCornerShape CutCornerShape(androidx.ui.core.Px topLeft = 0.px, androidx.ui.core.Px topRight = 0.px, androidx.ui.core.Px bottomRight = 0.px, androidx.ui.core.Px bottomLeft = 0.px);
+ method public static androidx.ui.material.shape.CutCornerShape CutCornerShape(@IntRange(from=0, to=50) int topLeftPercent = 0, @IntRange(from=0, to=50) int topRightPercent = 0, @IntRange(from=0, to=50) int bottomRightPercent = 0, @IntRange(from=0, to=50) int bottomLeftPercent = 0);
}
}
@@ -364,7 +380,7 @@
ctor public CardKt();
method public static void Card(androidx.ui.engine.geometry.Shape shape = RectangleShape, androidx.ui.graphics.Color color = +themeColor({
surface
-}), androidx.ui.foundation.shape.border.Border? border = null, androidx.ui.core.Dp elevation = 0.dp, kotlin.jvm.functions.Function0<kotlin.Unit> children);
+}), androidx.ui.foundation.shape.border.Border? border = null, androidx.ui.core.Dp elevation = 1.dp, kotlin.jvm.functions.Function0<kotlin.Unit> children);
}
public final class SurfaceKt {
diff --git a/ui/ui-material/api/current.txt b/ui/ui-material/api/current.txt
index 61a9c3f..f062c9e 100644
--- a/ui/ui-material/api/current.txt
+++ b/ui/ui-material/api/current.txt
@@ -45,7 +45,7 @@
primary
}), androidx.ui.engine.geometry.Shape shape = +themeShape({
button
-}), androidx.ui.core.Dp elevation = 0.dp);
+}), androidx.ui.core.Dp elevation = 2.dp);
method public static androidx.ui.material.ButtonStyle OutlinedButtonStyle(androidx.ui.foundation.shape.border.Border border = Border(+themeColor({
onSurface.copy(OutlinedStrokeOpacity)
}), 1.dp), androidx.ui.graphics.Color color = +themeColor({
@@ -108,13 +108,13 @@
ctor public FloatingActionButtonKt();
method public static void FloatingActionButton(kotlin.jvm.functions.Function0<kotlin.Unit>? onClick = null, androidx.ui.core.Dp minSize = FabSize, androidx.ui.engine.geometry.Shape shape = CircleShape, androidx.ui.graphics.Color color = +themeColor({
primary
-}), androidx.ui.core.Dp elevation = 0.dp, kotlin.jvm.functions.Function0<kotlin.Unit> children);
+}), androidx.ui.core.Dp elevation = 6.dp, kotlin.jvm.functions.Function0<kotlin.Unit> children);
method public static void FloatingActionButton(androidx.ui.painting.Image icon, kotlin.jvm.functions.Function0<kotlin.Unit>? onClick = null, androidx.ui.graphics.Color color = +themeColor({
primary
-}), androidx.ui.core.Dp elevation = 0.dp);
+}), androidx.ui.core.Dp elevation = 6.dp);
method public static void FloatingActionButton(String text, androidx.ui.painting.Image? icon = null, androidx.ui.text.TextStyle? textStyle = null, kotlin.jvm.functions.Function0<kotlin.Unit>? onClick = null, androidx.ui.graphics.Color color = +themeColor({
primary
-}), androidx.ui.core.Dp elevation = 0.dp);
+}), androidx.ui.core.Dp elevation = 6.dp);
method public static androidx.ui.core.Dp getExtendedFabHeight();
method public static androidx.ui.core.Dp getExtendedFabIconPadding();
method public static androidx.ui.core.Dp getExtendedFabTextPadding();
@@ -154,7 +154,6 @@
public final class MaterialThemeKt {
ctor public MaterialThemeKt();
method public static void MaterialButtonShapeTheme(kotlin.jvm.functions.Function0<kotlin.Unit> children);
- method public static void MaterialRippleTheme(kotlin.jvm.functions.Function0<kotlin.Unit> children);
method public static void MaterialTheme(androidx.ui.material.MaterialColors colors = androidx.ui.material.MaterialColors(), androidx.ui.material.MaterialTypography typography = androidx.ui.material.MaterialTypography(), kotlin.jvm.functions.Function0<kotlin.Unit> children);
method public static androidx.compose.Ambient<androidx.ui.material.MaterialColors> getColors();
method public static androidx.compose.Ambient<androidx.ui.material.Shapes> getCurrentShapeAmbient();
@@ -349,11 +348,28 @@
package androidx.ui.material.shape {
public final class CutCornerShape extends androidx.ui.foundation.shape.corner.CornerBasedShape {
- ctor public CutCornerShape(androidx.ui.foundation.shape.corner.CornerSizes corners);
- method public androidx.ui.foundation.shape.corner.CornerSizes component1();
- method public androidx.ui.material.shape.CutCornerShape copy(androidx.ui.foundation.shape.corner.CornerSizes corners);
- method public androidx.ui.engine.geometry.Outline.Generic createOutline(androidx.ui.foundation.shape.corner.PxCornerSizes corners, androidx.ui.core.PxSize size);
- method public androidx.ui.foundation.shape.corner.CornerSizes getCorners();
+ ctor public CutCornerShape(androidx.ui.foundation.shape.corner.CornerSize topLeft, androidx.ui.foundation.shape.corner.CornerSize topRight, androidx.ui.foundation.shape.corner.CornerSize bottomRight, androidx.ui.foundation.shape.corner.CornerSize bottomLeft);
+ method public androidx.ui.foundation.shape.corner.CornerSize component1();
+ method public androidx.ui.foundation.shape.corner.CornerSize component2();
+ method public androidx.ui.foundation.shape.corner.CornerSize component3();
+ method public androidx.ui.foundation.shape.corner.CornerSize component4();
+ method public androidx.ui.material.shape.CutCornerShape copy(androidx.ui.foundation.shape.corner.CornerSize topLeft, androidx.ui.foundation.shape.corner.CornerSize topRight, androidx.ui.foundation.shape.corner.CornerSize bottomRight, androidx.ui.foundation.shape.corner.CornerSize bottomLeft);
+ method public androidx.ui.engine.geometry.Outline.Generic createOutline(androidx.ui.core.PxSize size, androidx.ui.core.Px topLeft, androidx.ui.core.Px topRight, androidx.ui.core.Px bottomRight, androidx.ui.core.Px bottomLeft);
+ method public androidx.ui.foundation.shape.corner.CornerSize getBottomLeft();
+ method public androidx.ui.foundation.shape.corner.CornerSize getBottomRight();
+ method public androidx.ui.foundation.shape.corner.CornerSize getTopLeft();
+ method public androidx.ui.foundation.shape.corner.CornerSize getTopRight();
+ }
+
+ public final class CutCornerShapeKt {
+ ctor public CutCornerShapeKt();
+ method public static androidx.ui.material.shape.CutCornerShape CutCornerShape(androidx.ui.foundation.shape.corner.CornerSize corner);
+ method public static androidx.ui.material.shape.CutCornerShape CutCornerShape(androidx.ui.core.Dp size);
+ method public static androidx.ui.material.shape.CutCornerShape CutCornerShape(androidx.ui.core.Px size);
+ method public static androidx.ui.material.shape.CutCornerShape CutCornerShape(int percent);
+ method public static androidx.ui.material.shape.CutCornerShape CutCornerShape(androidx.ui.core.Dp topLeft = 0.dp, androidx.ui.core.Dp topRight = 0.dp, androidx.ui.core.Dp bottomRight = 0.dp, androidx.ui.core.Dp bottomLeft = 0.dp);
+ method public static androidx.ui.material.shape.CutCornerShape CutCornerShape(androidx.ui.core.Px topLeft = 0.px, androidx.ui.core.Px topRight = 0.px, androidx.ui.core.Px bottomRight = 0.px, androidx.ui.core.Px bottomLeft = 0.px);
+ method public static androidx.ui.material.shape.CutCornerShape CutCornerShape(@IntRange(from=0, to=50) int topLeftPercent = 0, @IntRange(from=0, to=50) int topRightPercent = 0, @IntRange(from=0, to=50) int bottomRightPercent = 0, @IntRange(from=0, to=50) int bottomLeftPercent = 0);
}
}
@@ -364,7 +380,7 @@
ctor public CardKt();
method public static void Card(androidx.ui.engine.geometry.Shape shape = RectangleShape, androidx.ui.graphics.Color color = +themeColor({
surface
-}), androidx.ui.foundation.shape.border.Border? border = null, androidx.ui.core.Dp elevation = 0.dp, kotlin.jvm.functions.Function0<kotlin.Unit> children);
+}), androidx.ui.foundation.shape.border.Border? border = null, androidx.ui.core.Dp elevation = 1.dp, kotlin.jvm.functions.Function0<kotlin.Unit> children);
}
public final class SurfaceKt {
diff --git a/ui/ui-material/api/restricted_1.0.0-alpha01.txt b/ui/ui-material/api/restricted_1.0.0-alpha01.txt
index 61a9c3f..f062c9e 100644
--- a/ui/ui-material/api/restricted_1.0.0-alpha01.txt
+++ b/ui/ui-material/api/restricted_1.0.0-alpha01.txt
@@ -45,7 +45,7 @@
primary
}), androidx.ui.engine.geometry.Shape shape = +themeShape({
button
-}), androidx.ui.core.Dp elevation = 0.dp);
+}), androidx.ui.core.Dp elevation = 2.dp);
method public static androidx.ui.material.ButtonStyle OutlinedButtonStyle(androidx.ui.foundation.shape.border.Border border = Border(+themeColor({
onSurface.copy(OutlinedStrokeOpacity)
}), 1.dp), androidx.ui.graphics.Color color = +themeColor({
@@ -108,13 +108,13 @@
ctor public FloatingActionButtonKt();
method public static void FloatingActionButton(kotlin.jvm.functions.Function0<kotlin.Unit>? onClick = null, androidx.ui.core.Dp minSize = FabSize, androidx.ui.engine.geometry.Shape shape = CircleShape, androidx.ui.graphics.Color color = +themeColor({
primary
-}), androidx.ui.core.Dp elevation = 0.dp, kotlin.jvm.functions.Function0<kotlin.Unit> children);
+}), androidx.ui.core.Dp elevation = 6.dp, kotlin.jvm.functions.Function0<kotlin.Unit> children);
method public static void FloatingActionButton(androidx.ui.painting.Image icon, kotlin.jvm.functions.Function0<kotlin.Unit>? onClick = null, androidx.ui.graphics.Color color = +themeColor({
primary
-}), androidx.ui.core.Dp elevation = 0.dp);
+}), androidx.ui.core.Dp elevation = 6.dp);
method public static void FloatingActionButton(String text, androidx.ui.painting.Image? icon = null, androidx.ui.text.TextStyle? textStyle = null, kotlin.jvm.functions.Function0<kotlin.Unit>? onClick = null, androidx.ui.graphics.Color color = +themeColor({
primary
-}), androidx.ui.core.Dp elevation = 0.dp);
+}), androidx.ui.core.Dp elevation = 6.dp);
method public static androidx.ui.core.Dp getExtendedFabHeight();
method public static androidx.ui.core.Dp getExtendedFabIconPadding();
method public static androidx.ui.core.Dp getExtendedFabTextPadding();
@@ -154,7 +154,6 @@
public final class MaterialThemeKt {
ctor public MaterialThemeKt();
method public static void MaterialButtonShapeTheme(kotlin.jvm.functions.Function0<kotlin.Unit> children);
- method public static void MaterialRippleTheme(kotlin.jvm.functions.Function0<kotlin.Unit> children);
method public static void MaterialTheme(androidx.ui.material.MaterialColors colors = androidx.ui.material.MaterialColors(), androidx.ui.material.MaterialTypography typography = androidx.ui.material.MaterialTypography(), kotlin.jvm.functions.Function0<kotlin.Unit> children);
method public static androidx.compose.Ambient<androidx.ui.material.MaterialColors> getColors();
method public static androidx.compose.Ambient<androidx.ui.material.Shapes> getCurrentShapeAmbient();
@@ -349,11 +348,28 @@
package androidx.ui.material.shape {
public final class CutCornerShape extends androidx.ui.foundation.shape.corner.CornerBasedShape {
- ctor public CutCornerShape(androidx.ui.foundation.shape.corner.CornerSizes corners);
- method public androidx.ui.foundation.shape.corner.CornerSizes component1();
- method public androidx.ui.material.shape.CutCornerShape copy(androidx.ui.foundation.shape.corner.CornerSizes corners);
- method public androidx.ui.engine.geometry.Outline.Generic createOutline(androidx.ui.foundation.shape.corner.PxCornerSizes corners, androidx.ui.core.PxSize size);
- method public androidx.ui.foundation.shape.corner.CornerSizes getCorners();
+ ctor public CutCornerShape(androidx.ui.foundation.shape.corner.CornerSize topLeft, androidx.ui.foundation.shape.corner.CornerSize topRight, androidx.ui.foundation.shape.corner.CornerSize bottomRight, androidx.ui.foundation.shape.corner.CornerSize bottomLeft);
+ method public androidx.ui.foundation.shape.corner.CornerSize component1();
+ method public androidx.ui.foundation.shape.corner.CornerSize component2();
+ method public androidx.ui.foundation.shape.corner.CornerSize component3();
+ method public androidx.ui.foundation.shape.corner.CornerSize component4();
+ method public androidx.ui.material.shape.CutCornerShape copy(androidx.ui.foundation.shape.corner.CornerSize topLeft, androidx.ui.foundation.shape.corner.CornerSize topRight, androidx.ui.foundation.shape.corner.CornerSize bottomRight, androidx.ui.foundation.shape.corner.CornerSize bottomLeft);
+ method public androidx.ui.engine.geometry.Outline.Generic createOutline(androidx.ui.core.PxSize size, androidx.ui.core.Px topLeft, androidx.ui.core.Px topRight, androidx.ui.core.Px bottomRight, androidx.ui.core.Px bottomLeft);
+ method public androidx.ui.foundation.shape.corner.CornerSize getBottomLeft();
+ method public androidx.ui.foundation.shape.corner.CornerSize getBottomRight();
+ method public androidx.ui.foundation.shape.corner.CornerSize getTopLeft();
+ method public androidx.ui.foundation.shape.corner.CornerSize getTopRight();
+ }
+
+ public final class CutCornerShapeKt {
+ ctor public CutCornerShapeKt();
+ method public static androidx.ui.material.shape.CutCornerShape CutCornerShape(androidx.ui.foundation.shape.corner.CornerSize corner);
+ method public static androidx.ui.material.shape.CutCornerShape CutCornerShape(androidx.ui.core.Dp size);
+ method public static androidx.ui.material.shape.CutCornerShape CutCornerShape(androidx.ui.core.Px size);
+ method public static androidx.ui.material.shape.CutCornerShape CutCornerShape(int percent);
+ method public static androidx.ui.material.shape.CutCornerShape CutCornerShape(androidx.ui.core.Dp topLeft = 0.dp, androidx.ui.core.Dp topRight = 0.dp, androidx.ui.core.Dp bottomRight = 0.dp, androidx.ui.core.Dp bottomLeft = 0.dp);
+ method public static androidx.ui.material.shape.CutCornerShape CutCornerShape(androidx.ui.core.Px topLeft = 0.px, androidx.ui.core.Px topRight = 0.px, androidx.ui.core.Px bottomRight = 0.px, androidx.ui.core.Px bottomLeft = 0.px);
+ method public static androidx.ui.material.shape.CutCornerShape CutCornerShape(@IntRange(from=0, to=50) int topLeftPercent = 0, @IntRange(from=0, to=50) int topRightPercent = 0, @IntRange(from=0, to=50) int bottomRightPercent = 0, @IntRange(from=0, to=50) int bottomLeftPercent = 0);
}
}
@@ -364,7 +380,7 @@
ctor public CardKt();
method public static void Card(androidx.ui.engine.geometry.Shape shape = RectangleShape, androidx.ui.graphics.Color color = +themeColor({
surface
-}), androidx.ui.foundation.shape.border.Border? border = null, androidx.ui.core.Dp elevation = 0.dp, kotlin.jvm.functions.Function0<kotlin.Unit> children);
+}), androidx.ui.foundation.shape.border.Border? border = null, androidx.ui.core.Dp elevation = 1.dp, kotlin.jvm.functions.Function0<kotlin.Unit> children);
}
public final class SurfaceKt {
diff --git a/ui/ui-material/api/restricted_current.txt b/ui/ui-material/api/restricted_current.txt
index 61a9c3f..f062c9e 100644
--- a/ui/ui-material/api/restricted_current.txt
+++ b/ui/ui-material/api/restricted_current.txt
@@ -45,7 +45,7 @@
primary
}), androidx.ui.engine.geometry.Shape shape = +themeShape({
button
-}), androidx.ui.core.Dp elevation = 0.dp);
+}), androidx.ui.core.Dp elevation = 2.dp);
method public static androidx.ui.material.ButtonStyle OutlinedButtonStyle(androidx.ui.foundation.shape.border.Border border = Border(+themeColor({
onSurface.copy(OutlinedStrokeOpacity)
}), 1.dp), androidx.ui.graphics.Color color = +themeColor({
@@ -108,13 +108,13 @@
ctor public FloatingActionButtonKt();
method public static void FloatingActionButton(kotlin.jvm.functions.Function0<kotlin.Unit>? onClick = null, androidx.ui.core.Dp minSize = FabSize, androidx.ui.engine.geometry.Shape shape = CircleShape, androidx.ui.graphics.Color color = +themeColor({
primary
-}), androidx.ui.core.Dp elevation = 0.dp, kotlin.jvm.functions.Function0<kotlin.Unit> children);
+}), androidx.ui.core.Dp elevation = 6.dp, kotlin.jvm.functions.Function0<kotlin.Unit> children);
method public static void FloatingActionButton(androidx.ui.painting.Image icon, kotlin.jvm.functions.Function0<kotlin.Unit>? onClick = null, androidx.ui.graphics.Color color = +themeColor({
primary
-}), androidx.ui.core.Dp elevation = 0.dp);
+}), androidx.ui.core.Dp elevation = 6.dp);
method public static void FloatingActionButton(String text, androidx.ui.painting.Image? icon = null, androidx.ui.text.TextStyle? textStyle = null, kotlin.jvm.functions.Function0<kotlin.Unit>? onClick = null, androidx.ui.graphics.Color color = +themeColor({
primary
-}), androidx.ui.core.Dp elevation = 0.dp);
+}), androidx.ui.core.Dp elevation = 6.dp);
method public static androidx.ui.core.Dp getExtendedFabHeight();
method public static androidx.ui.core.Dp getExtendedFabIconPadding();
method public static androidx.ui.core.Dp getExtendedFabTextPadding();
@@ -154,7 +154,6 @@
public final class MaterialThemeKt {
ctor public MaterialThemeKt();
method public static void MaterialButtonShapeTheme(kotlin.jvm.functions.Function0<kotlin.Unit> children);
- method public static void MaterialRippleTheme(kotlin.jvm.functions.Function0<kotlin.Unit> children);
method public static void MaterialTheme(androidx.ui.material.MaterialColors colors = androidx.ui.material.MaterialColors(), androidx.ui.material.MaterialTypography typography = androidx.ui.material.MaterialTypography(), kotlin.jvm.functions.Function0<kotlin.Unit> children);
method public static androidx.compose.Ambient<androidx.ui.material.MaterialColors> getColors();
method public static androidx.compose.Ambient<androidx.ui.material.Shapes> getCurrentShapeAmbient();
@@ -349,11 +348,28 @@
package androidx.ui.material.shape {
public final class CutCornerShape extends androidx.ui.foundation.shape.corner.CornerBasedShape {
- ctor public CutCornerShape(androidx.ui.foundation.shape.corner.CornerSizes corners);
- method public androidx.ui.foundation.shape.corner.CornerSizes component1();
- method public androidx.ui.material.shape.CutCornerShape copy(androidx.ui.foundation.shape.corner.CornerSizes corners);
- method public androidx.ui.engine.geometry.Outline.Generic createOutline(androidx.ui.foundation.shape.corner.PxCornerSizes corners, androidx.ui.core.PxSize size);
- method public androidx.ui.foundation.shape.corner.CornerSizes getCorners();
+ ctor public CutCornerShape(androidx.ui.foundation.shape.corner.CornerSize topLeft, androidx.ui.foundation.shape.corner.CornerSize topRight, androidx.ui.foundation.shape.corner.CornerSize bottomRight, androidx.ui.foundation.shape.corner.CornerSize bottomLeft);
+ method public androidx.ui.foundation.shape.corner.CornerSize component1();
+ method public androidx.ui.foundation.shape.corner.CornerSize component2();
+ method public androidx.ui.foundation.shape.corner.CornerSize component3();
+ method public androidx.ui.foundation.shape.corner.CornerSize component4();
+ method public androidx.ui.material.shape.CutCornerShape copy(androidx.ui.foundation.shape.corner.CornerSize topLeft, androidx.ui.foundation.shape.corner.CornerSize topRight, androidx.ui.foundation.shape.corner.CornerSize bottomRight, androidx.ui.foundation.shape.corner.CornerSize bottomLeft);
+ method public androidx.ui.engine.geometry.Outline.Generic createOutline(androidx.ui.core.PxSize size, androidx.ui.core.Px topLeft, androidx.ui.core.Px topRight, androidx.ui.core.Px bottomRight, androidx.ui.core.Px bottomLeft);
+ method public androidx.ui.foundation.shape.corner.CornerSize getBottomLeft();
+ method public androidx.ui.foundation.shape.corner.CornerSize getBottomRight();
+ method public androidx.ui.foundation.shape.corner.CornerSize getTopLeft();
+ method public androidx.ui.foundation.shape.corner.CornerSize getTopRight();
+ }
+
+ public final class CutCornerShapeKt {
+ ctor public CutCornerShapeKt();
+ method public static androidx.ui.material.shape.CutCornerShape CutCornerShape(androidx.ui.foundation.shape.corner.CornerSize corner);
+ method public static androidx.ui.material.shape.CutCornerShape CutCornerShape(androidx.ui.core.Dp size);
+ method public static androidx.ui.material.shape.CutCornerShape CutCornerShape(androidx.ui.core.Px size);
+ method public static androidx.ui.material.shape.CutCornerShape CutCornerShape(int percent);
+ method public static androidx.ui.material.shape.CutCornerShape CutCornerShape(androidx.ui.core.Dp topLeft = 0.dp, androidx.ui.core.Dp topRight = 0.dp, androidx.ui.core.Dp bottomRight = 0.dp, androidx.ui.core.Dp bottomLeft = 0.dp);
+ method public static androidx.ui.material.shape.CutCornerShape CutCornerShape(androidx.ui.core.Px topLeft = 0.px, androidx.ui.core.Px topRight = 0.px, androidx.ui.core.Px bottomRight = 0.px, androidx.ui.core.Px bottomLeft = 0.px);
+ method public static androidx.ui.material.shape.CutCornerShape CutCornerShape(@IntRange(from=0, to=50) int topLeftPercent = 0, @IntRange(from=0, to=50) int topRightPercent = 0, @IntRange(from=0, to=50) int bottomRightPercent = 0, @IntRange(from=0, to=50) int bottomLeftPercent = 0);
}
}
@@ -364,7 +380,7 @@
ctor public CardKt();
method public static void Card(androidx.ui.engine.geometry.Shape shape = RectangleShape, androidx.ui.graphics.Color color = +themeColor({
surface
-}), androidx.ui.foundation.shape.border.Border? border = null, androidx.ui.core.Dp elevation = 0.dp, kotlin.jvm.functions.Function0<kotlin.Unit> children);
+}), androidx.ui.foundation.shape.border.Border? border = null, androidx.ui.core.Dp elevation = 1.dp, kotlin.jvm.functions.Function0<kotlin.Unit> children);
}
public final class SurfaceKt {
diff --git a/ui/ui-material/integration-tests/material-demos/src/main/java/androidx/ui/material/demos/RippleDemo.kt b/ui/ui-material/integration-tests/material-demos/src/main/java/androidx/ui/material/demos/RippleDemo.kt
index ebb0aa1..d93ad3f 100644
--- a/ui/ui-material/integration-tests/material-demos/src/main/java/androidx/ui/material/demos/RippleDemo.kt
+++ b/ui/ui-material/integration-tests/material-demos/src/main/java/androidx/ui/material/demos/RippleDemo.kt
@@ -24,7 +24,6 @@
import androidx.ui.core.dp
import androidx.ui.core.px
import androidx.ui.foundation.shape.border.Border
-import androidx.ui.foundation.shape.corner.CornerSizes
import androidx.ui.foundation.shape.corner.RoundedCornerShape
import androidx.ui.graphics.Color
import androidx.ui.layout.Container
@@ -40,7 +39,7 @@
MaterialTheme {
Padding(padding = 50.dp) {
Card(
- shape = RoundedCornerShape(CornerSizes(100.px)),
+ shape = RoundedCornerShape(100.px),
border = Border(Color(0x80000000.toInt()), 1.dp)
) {
Ripple(bounded = true) {
diff --git a/ui/ui-material/integration-tests/samples/src/main/java/androidx/ui/material/samples/TabSamples.kt b/ui/ui-material/integration-tests/samples/src/main/java/androidx/ui/material/samples/TabSamples.kt
index 058f62f..647f4c9 100644
--- a/ui/ui-material/integration-tests/samples/src/main/java/androidx/ui/material/samples/TabSamples.kt
+++ b/ui/ui-material/integration-tests/samples/src/main/java/androidx/ui/material/samples/TabSamples.kt
@@ -32,7 +32,6 @@
import androidx.ui.foundation.selection.MutuallyExclusiveSetItem
import androidx.ui.foundation.shape.border.Border
import androidx.ui.foundation.shape.border.DrawBorder
-import androidx.ui.foundation.shape.corner.CornerSizes
import androidx.ui.foundation.shape.corner.RoundedCornerShape
import androidx.ui.graphics.Color
import androidx.ui.layout.Alignment
@@ -230,7 +229,7 @@
// Color is passed in as a parameter [color]
Padding(5.dp) {
Container(expanded = true) {
- DrawBorder(RoundedCornerShape(CornerSizes(5.dp)), Border(color, 2.dp))
+ DrawBorder(RoundedCornerShape(5.dp), Border(color, 2.dp))
}
}
}
diff --git a/ui/ui-material/integration-tests/samples/src/main/java/androidx/ui/material/samples/ThemeSamples.kt b/ui/ui-material/integration-tests/samples/src/main/java/androidx/ui/material/samples/ThemeSamples.kt
new file mode 100644
index 0000000..5e45c43
--- /dev/null
+++ b/ui/ui-material/integration-tests/samples/src/main/java/androidx/ui/material/samples/ThemeSamples.kt
@@ -0,0 +1,68 @@
+/*
+ * 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.material.samples
+
+import androidx.annotation.Sampled
+import androidx.compose.Composable
+import androidx.compose.composer
+import androidx.compose.unaryPlus
+import androidx.ui.core.Text
+import androidx.ui.core.sp
+import androidx.ui.foundation.ColoredRect
+import androidx.ui.graphics.Color
+import androidx.ui.material.MaterialColors
+import androidx.ui.material.MaterialTheme
+import androidx.ui.material.MaterialTypography
+import androidx.ui.material.themeColor
+import androidx.ui.material.themeTextStyle
+import androidx.ui.text.TextStyle
+import androidx.ui.text.font.FontFamily
+import androidx.ui.text.font.FontWeight
+
+@Sampled
+@Composable
+fun MaterialThemeSample() {
+ val colors = MaterialColors(
+ primary = Color(0xFF1EB980.toInt()),
+ surface = Color(0xFF26282F.toInt()),
+ onSurface = Color.White
+ )
+ val typography = MaterialTypography(
+ h1 = TextStyle(fontFamily = FontFamily("RobotoCondensed"),
+ fontWeight = FontWeight.w100,
+ fontSize = 96.sp),
+ h2 = TextStyle(fontFamily = FontFamily("RobotoCondensed"),
+ fontWeight = FontWeight.w100,
+ fontSize = 60.sp)
+
+ )
+ MaterialTheme(colors = colors, typography = typography) {
+ // Your app content goes here
+ }
+}
+
+@Sampled
+@Composable
+fun ThemeColorSample() {
+ ColoredRect(color = +themeColor { primary })
+}
+
+@Sampled
+@Composable
+fun ThemeTextStyleSample() {
+ Text(text = "Styled text", style = +themeTextStyle { h1 })
+}
\ No newline at end of file
diff --git a/ui/ui-material/src/androidTest/java/androidx/ui/material/CheckboxUiTest.kt b/ui/ui-material/src/androidTest/java/androidx/ui/material/CheckboxUiTest.kt
index 376e7d8..2572b53 100644
--- a/ui/ui-material/src/androidTest/java/androidx/ui/material/CheckboxUiTest.kt
+++ b/ui/ui-material/src/androidTest/java/androidx/ui/material/CheckboxUiTest.kt
@@ -29,6 +29,7 @@
import androidx.ui.foundation.semantics.toggleableState
import androidx.ui.layout.Column
import androidx.ui.semantics.accessibilityValue
+import androidx.ui.test.assertHasNoClickAction
import androidx.ui.test.assertIsChecked
import androidx.ui.test.assertIsUnchecked
import androidx.ui.test.assertSemanticsIsEqualTo
@@ -126,9 +127,7 @@
}
findByTag(defaultTag)
- .assertIsUnchecked()
- .doClick()
- .assertIsUnchecked()
+ .assertHasNoClickAction()
}
@Test
diff --git a/ui/ui-material/src/androidTest/java/androidx/ui/material/SwitchUiTest.kt b/ui/ui-material/src/androidTest/java/androidx/ui/material/SwitchUiTest.kt
index 7d4afcf..4bd7229 100644
--- a/ui/ui-material/src/androidTest/java/androidx/ui/material/SwitchUiTest.kt
+++ b/ui/ui-material/src/androidTest/java/androidx/ui/material/SwitchUiTest.kt
@@ -27,6 +27,7 @@
import androidx.ui.foundation.semantics.toggleableState
import androidx.ui.layout.Column
import androidx.ui.semantics.accessibilityValue
+import androidx.ui.test.assertHasNoClickAction
import androidx.ui.test.assertIsChecked
import androidx.ui.test.assertIsUnchecked
import androidx.ui.test.assertSemanticsIsEqualTo
@@ -116,9 +117,7 @@
}
}
findByTag(defaultSwitchTag)
- .assertIsUnchecked()
- .doClick()
- .assertIsUnchecked()
+ .assertHasNoClickAction()
}
@Test
diff --git a/ui/ui-material/src/androidTest/java/androidx/ui/material/shape/CutCornerShapeTest.kt b/ui/ui-material/src/androidTest/java/androidx/ui/material/shape/CutCornerShapeTest.kt
index ff8249b..4c1072d 100644
--- a/ui/ui-material/src/androidTest/java/androidx/ui/material/shape/CutCornerShapeTest.kt
+++ b/ui/ui-material/src/androidTest/java/androidx/ui/material/shape/CutCornerShapeTest.kt
@@ -22,8 +22,6 @@
import androidx.ui.core.px
import androidx.ui.engine.geometry.Outline
import androidx.ui.engine.geometry.Shape
-import androidx.ui.foundation.shape.corner.CornerSize
-import androidx.ui.foundation.shape.corner.CornerSizes
import androidx.ui.painting.Path
import androidx.ui.painting.PathOperation
import com.google.common.truth.Truth.assertThat
@@ -41,13 +39,7 @@
@Test
fun cutCornersUniformCorners() {
- val cut = CutCornerShape(
- CornerSizes(
- CornerSize(
- 10.px
- )
- )
- )
+ val cut = CutCornerShape(10.px)
val outline = cut.toOutline() as Outline.Generic
assertPathsEquals(outline.path, Path().apply {
@@ -69,14 +61,7 @@
val size2 = 22f
val size3 = 32f
val size4 = 42f
- val cut = CutCornerShape(
- CornerSizes(
- CornerSize(size1.px),
- CornerSize(size2.px),
- CornerSize(size3.px),
- CornerSize(size4.px)
- )
- )
+ val cut = CutCornerShape(size1.px, size2.px, size3.px, size4.px)
val outline = cut.toOutline() as Outline.Generic
assertPathsEquals(outline.path, Path().apply {
@@ -94,8 +79,8 @@
@Test
fun cutCornerShapesAreEquals() {
- assertThat(CutCornerShape(CornerSizes(10.px)))
- .isEqualTo(CutCornerShape(CornerSizes(10.px)))
+ assertThat(CutCornerShape(10.px))
+ .isEqualTo(CutCornerShape(10.px))
}
private fun Shape.toOutline() = createOutline(size, density)
diff --git a/ui/ui-material/src/main/java/androidx/ui/material/AlertDialog.kt b/ui/ui-material/src/main/java/androidx/ui/material/AlertDialog.kt
index 010118a..7b09461 100644
--- a/ui/ui-material/src/main/java/androidx/ui/material/AlertDialog.kt
+++ b/ui/ui-material/src/main/java/androidx/ui/material/AlertDialog.kt
@@ -23,8 +23,6 @@
import androidx.ui.core.CurrentTextStyleProvider
import androidx.ui.core.dp
import androidx.ui.foundation.Dialog
-import androidx.ui.foundation.shape.corner.CornerSize
-import androidx.ui.foundation.shape.corner.CornerSizes
import androidx.ui.foundation.shape.corner.RoundedCornerShape
import androidx.ui.layout.Alignment
import androidx.ui.layout.Column
@@ -176,6 +174,5 @@
// The height difference of the padding between a Dialog with a title and one without a title
private val NoTitleExtraHeight = 2.dp
private val TextToButtonsHeight = 28.dp
-private val ShapeCornerSize = CornerSizes(CornerSize(4.dp))
// TODO: The corner radius should be customizable
-private val AlertDialogShape = RoundedCornerShape(ShapeCornerSize)
\ No newline at end of file
+private val AlertDialogShape = RoundedCornerShape(4.dp)
\ No newline at end of file
diff --git a/ui/ui-material/src/main/java/androidx/ui/material/Button.kt b/ui/ui-material/src/main/java/androidx/ui/material/Button.kt
index 614ae7f..f8b7b54 100644
--- a/ui/ui-material/src/main/java/androidx/ui/material/Button.kt
+++ b/ui/ui-material/src/main/java/androidx/ui/material/Button.kt
@@ -82,7 +82,7 @@
fun ContainedButtonStyle(
color: Color = +themeColor { primary },
shape: Shape = +themeShape { button },
- elevation: Dp = 0.dp // TODO update to 2.dp when DrawShadow will be ready
+ elevation: Dp = 2.dp
) = ButtonStyle(
color = color,
shape = shape,
diff --git a/ui/ui-material/src/main/java/androidx/ui/material/Drawer.kt b/ui/ui-material/src/main/java/androidx/ui/material/Drawer.kt
index 547149d8..7e5e527 100644
--- a/ui/ui-material/src/main/java/androidx/ui/material/Drawer.kt
+++ b/ui/ui-material/src/main/java/androidx/ui/material/Drawer.kt
@@ -298,7 +298,7 @@
RepaintBoundary {
child()
}
- }, layoutBlock = { measurables, constraints ->
+ }) { measurables, constraints ->
if (measurables.size > 1) {
throw IllegalStateException("Only one child is allowed")
}
@@ -316,7 +316,7 @@
layout(width, height) {
placeable?.place(xOffset.toIntPx(), yOffset.toIntPx())
}
- })
+ }
}
private val ScrimDefaultOpacity = 0.32f
diff --git a/ui/ui-material/src/main/java/androidx/ui/material/FloatingActionButton.kt b/ui/ui-material/src/main/java/androidx/ui/material/FloatingActionButton.kt
index 070a382..28a1e28 100644
--- a/ui/ui-material/src/main/java/androidx/ui/material/FloatingActionButton.kt
+++ b/ui/ui-material/src/main/java/androidx/ui/material/FloatingActionButton.kt
@@ -62,7 +62,7 @@
minSize: Dp = FabSize,
shape: Shape = CircleShape,
color: Color = +themeColor { primary },
- elevation: Dp = 0.dp, // TODO(Andrey) add the default elevation when it ready b/123215187
+ elevation: Dp = 6.dp,
children: @Composable() () -> Unit
) {
BaseButton(
@@ -99,7 +99,7 @@
icon: Image,
onClick: (() -> Unit)? = null,
color: Color = +themeColor { primary },
- elevation: Dp = 0.dp
+ elevation: Dp = 6.dp
) {
FloatingActionButton(onClick = onClick, color = color, elevation = elevation) {
SimpleImage(image = icon)
@@ -130,7 +130,7 @@
textStyle: TextStyle? = null,
onClick: (() -> Unit)? = null,
color: Color = +themeColor { primary },
- elevation: Dp = 0.dp
+ elevation: Dp = 6.dp
) {
FloatingActionButton(
onClick = onClick,
diff --git a/ui/ui-material/src/main/java/androidx/ui/material/MaterialTheme.kt b/ui/ui-material/src/main/java/androidx/ui/material/MaterialTheme.kt
index 76ce952..6ca12a8 100644
--- a/ui/ui-material/src/main/java/androidx/ui/material/MaterialTheme.kt
+++ b/ui/ui-material/src/main/java/androidx/ui/material/MaterialTheme.kt
@@ -18,7 +18,6 @@
import androidx.annotation.CheckResult
import androidx.compose.Ambient
-import androidx.compose.Children
import androidx.compose.Composable
import androidx.compose.composer
import androidx.compose.ambient
@@ -30,7 +29,6 @@
import androidx.ui.core.sp
import androidx.ui.core.withDensity
import androidx.ui.engine.geometry.Shape
-import androidx.ui.foundation.shape.corner.CornerSizes
import androidx.ui.foundation.shape.corner.RoundedCornerShape
import androidx.ui.graphics.Color
import androidx.ui.material.ripple.CurrentRippleTheme
@@ -41,17 +39,22 @@
import androidx.ui.text.TextStyle
/**
- * This Component defines the styling principles from the Material design specification. It must be
+ * This component defines the styling principles from the Material design specification. It must be
* present within a hierarchy of components that includes Material components, as it defines key
* values such as base colors and typography.
*
- * By default, it defines the colors as specified in the Color theme creation spec
- * (https://material.io/design/color/the-color-system.html#color-theme-creation) and the typography
- * defined in the Type Scale spec
- * (https://material.io/design/typography/the-type-system.html#type-scale).
+ * Material components such as [Button] and [Checkbox] use this definition to set default values.
*
- * All values may be overriden by providing this component with the [colors] and [typography]
- * attributes. Use this to configure the overall theme of your application.
+ * It defines colors as specified in the [Material Color theme creation spec]
+ * [https://material.io/design/color/the-color-system.html#color-theme-creation] and the typography
+ * defined in the [Material Type Scale spec]
+ * [https://material.io/design/typography/the-type-system.html#type-scale].
+ *
+ * All values may be set by providing this component with the [colors][MaterialColors] and
+ * [typography][MaterialTypography] attributes. Use this to configure the overall theme of your
+ * application.
+ *
+ * @sample androidx.ui.material.samples.MaterialThemeSample
*/
@Composable
fun MaterialTheme(
@@ -75,21 +78,25 @@
* by the Material spec. You can read the values in it when creating custom components that want
* to use Material colors, as well as override the values when you want to re-style a part of your
* hierarchy.
+ *
+ * To access values within this ambient, use [themeColor].
*/
val Colors = Ambient.of<MaterialColors>("colors") { error("No colors found!") }
/**
- * This Ambient holds on to the current definiton of typography for this application as described
+ * This Ambient holds on to the current definition of typography for this application as described
* by the Material spec. You can read the values in it when creating custom components that want
* to use Material types, as well as override the values when you want to re-style a part of your
- * hierarchy. Material components related to text such as [H1TextStyle] will refer to this Ambient
- * to obtain the values with which to style text.
+ * hierarchy. Material components related to text such as [Button] will use this Ambient
+ * to set values with which to style children text components.
+ *
+ * To access values within this ambient, use [themeTextStyle].
*/
val Typography = Ambient.of<MaterialTypography>("typography") { error("No typography found!") }
/**
- * Data class holding color values as defined by the Material specification
- * (https://material.io/design/color/the-color-system.html#color-theme-creation).
+ * Data class holding color values as defined by the [Material color specification]
+ * [https://material.io/design/color/the-color-system.html#color-theme-creation].
*/
data class MaterialColors(
/**
@@ -98,25 +105,23 @@
*/
val primary: Color = Color(0xFF6200EE.toInt()),
/**
- * The primary variant is used to distinguish two elements of the app using the primary color,
- * such as the top app bar and the system bar.
+ * The primary variant color is used to distinguish two elements of the app using the primary
+ * color, such as the top app bar and the system bar.
*/
val primaryVariant: Color = Color(0xFF3700B3.toInt()),
/**
* The secondary color provides more ways to accent and distinguish your product.
* Secondary colors are best for:
- * <ul>
- * <li>Floating action buttons</li>
- * <li>Selection controls, like sliders and switches</li>
- * <li>Highlighting selected text</li>
- * <li>Progress bars</li>
- * <li>Links and headlines</li>
- * </ul>
+ * - Floating action buttons
+ * - Selection controls, like sliders and switches
+ * - Highlighting selected text
+ * - Progress bars
+ * - Links and headlines
*/
val secondary: Color = Color(0xFF03DAC6.toInt()),
/**
- * The secondary variant is used to distinguish two elements of the app using the secondary
- * color.
+ * The secondary variant color is used to distinguish two elements of the app using the
+ * secondary color.
*/
val secondaryVariant: Color = Color(0xFF018786.toInt()),
/**
@@ -154,8 +159,8 @@
)
/**
- * Data class holding typography definitions as defined by the Material specification
- * (https://material.io/design/typography/the-type-system.html#type-scale).
+ * Data class holding typography definitions as defined by the [Material typography specification]
+ * [https://material.io/design/typography/the-type-system.html#type-scale].
*/
data class MaterialTypography(
// TODO(clara): case
@@ -221,7 +226,7 @@
* guidelines for descendants
*/
@Composable
-fun MaterialRippleTheme(children: @Composable() () -> Unit) {
+private fun MaterialRippleTheme(children: @Composable() () -> Unit) {
val materialColors = +ambient(Colors)
val defaultTheme = +memo {
RippleTheme(
@@ -241,7 +246,9 @@
}
/**
- * Helps to resolve the [Color] by applying [choosingBlock] for the [MaterialColors].
+ * Helper effect that resolves [Color]s from the [Colors] ambient by applying [choosingBlock].
+ *
+ * @sample androidx.ui.material.samples.ThemeColorSample
*/
@CheckResult(suggest = "+")
fun themeColor(
@@ -249,7 +256,10 @@
) = effectOf<Color> { (+ambient(Colors)).choosingBlock() }
/**
- * Helps to resolve the [TextStyle] by applying [choosingBlock] for the [MaterialTypography].
+ * Helper effect that resolves [TextStyle]s from the [Typography] ambient by applying
+ * [choosingBlock].
+ *
+ * @sample androidx.ui.material.samples.ThemeTextStyleSample
*/
@CheckResult(suggest = "+")
fun themeTextStyle(
@@ -289,14 +299,15 @@
fun MaterialButtonShapeTheme(children: @Composable() () -> Unit) {
val value = +withDensity {
Shapes(
- button = RoundedCornerShape(CornerSizes(4.dp))
+ button = RoundedCornerShape(4.dp)
)
}
CurrentShapeAmbient.Provider(value = value, children = children)
}
/**
- * Helps to resolve the [Shape] by applying [choosingBlock] for the [Shapes].
+ * Helper effect to resolve [Shape]s from the [CurrentShapeAmbient] ambient by applying
+ * [choosingBlock].
*/
@CheckResult(suggest = "+")
fun themeShape(
diff --git a/ui/ui-material/src/main/java/androidx/ui/material/ripple/RippleTheme.kt b/ui/ui-material/src/main/java/androidx/ui/material/ripple/RippleTheme.kt
index c428435..2622055b 100644
--- a/ui/ui-material/src/main/java/androidx/ui/material/ripple/RippleTheme.kt
+++ b/ui/ui-material/src/main/java/androidx/ui/material/ripple/RippleTheme.kt
@@ -16,19 +16,15 @@
package androidx.ui.material.ripple
-import androidx.ui.material.MaterialRippleTheme
import androidx.ui.graphics.Color
import androidx.compose.Ambient
/**
- * Defines the appearance and the behavior for [RippleEffect]s.
- * Used for customisation of [Ripple].
+ * Defines the appearance and the behavior for [RippleEffect]s. Used for customisation of [Ripple].
*
- * To change some parameter and apply it to descendants modify
- * the [CurrentRippleTheme] ambient.
+ * To change some parameter and apply it to descendants modify the [CurrentRippleTheme] ambient.
*
- * To apply the default values based on the Material Design guidelines
- * use [MaterialRippleTheme].
+ * To apply the default values based on the Material Design guidelines use [androidx.ui.material.MaterialTheme].
*/
data class RippleTheme(
/**
@@ -48,5 +44,5 @@
typealias RippleColorCallback = (background: Color?) -> (Color)
val CurrentRippleTheme = Ambient.of<RippleTheme> {
- error("No RippleTheme provided. Please add MaterialRippleTheme as an ancestor.")
+ error("No RippleTheme provided. Please add MaterialTheme as an ancestor.")
}
diff --git a/ui/ui-material/src/main/java/androidx/ui/material/shape/CutCornerShape.kt b/ui/ui-material/src/main/java/androidx/ui/material/shape/CutCornerShape.kt
index 8f14f3d..5226f09 100644
--- a/ui/ui-material/src/main/java/androidx/ui/material/shape/CutCornerShape.kt
+++ b/ui/ui-material/src/main/java/androidx/ui/material/shape/CutCornerShape.kt
@@ -16,37 +16,117 @@
package androidx.ui.material.shape
+import androidx.annotation.IntRange
+import androidx.ui.core.Dp
+import androidx.ui.core.Px
import androidx.ui.core.PxSize
+import androidx.ui.core.dp
+import androidx.ui.core.px
import androidx.ui.engine.geometry.Outline
import androidx.ui.foundation.shape.corner.CornerBasedShape
-import androidx.ui.foundation.shape.corner.CornerSizes
-import androidx.ui.foundation.shape.corner.PxCornerSizes
+import androidx.ui.foundation.shape.corner.CornerSize
import androidx.ui.painting.Path
/**
* A shape describing the rectangle with cut corners.
* Corner size is representing the cut length - the size of both legs of the cut's right triangle.
*
- * @param corners define all four corner sizes
+ * @param topLeft a size of the top left corner
+ * @param topRight a size of the top right corner
+ * @param bottomRight a size of the bottom left corner
+ * @param bottomLeft a size of the bottom right corner
*/
data class CutCornerShape(
- val corners: CornerSizes
-) : CornerBasedShape(corners) {
+ val topLeft: CornerSize,
+ val topRight: CornerSize,
+ val bottomRight: CornerSize,
+ val bottomLeft: CornerSize
+) : CornerBasedShape(topLeft, topRight, bottomRight, bottomLeft) {
- override fun createOutline(corners: PxCornerSizes, size: PxSize) =
- Outline.Generic(Path().apply {
- var cornerSize = corners.topLeft.value
- moveTo(0f, cornerSize)
- lineTo(cornerSize, 0f)
- cornerSize = corners.topRight.value
- lineTo(size.width.value - cornerSize, 0f)
- lineTo(size.width.value, cornerSize)
- cornerSize = corners.bottomRight.value
- lineTo(size.width.value, size.height.value - cornerSize)
- lineTo(size.width.value - cornerSize, size.height.value)
- cornerSize = corners.bottomLeft.value
- lineTo(cornerSize, size.height.value)
- lineTo(0f, size.height.value - cornerSize)
- close()
- })
+ override fun createOutline(
+ size: PxSize,
+ topLeft: Px,
+ topRight: Px,
+ bottomRight: Px,
+ bottomLeft: Px
+ ) = Outline.Generic(Path().apply {
+ var cornerSize = topLeft.value
+ moveTo(0f, cornerSize)
+ lineTo(cornerSize, 0f)
+ cornerSize = topRight.value
+ lineTo(size.width.value - cornerSize, 0f)
+ lineTo(size.width.value, cornerSize)
+ cornerSize = bottomRight.value
+ lineTo(size.width.value, size.height.value - cornerSize)
+ lineTo(size.width.value - cornerSize, size.height.value)
+ cornerSize = bottomLeft.value
+ lineTo(cornerSize, size.height.value)
+ lineTo(0f, size.height.value - cornerSize)
+ close()
+ })
}
+
+/**
+ * Creates [CutCornerShape] with the same size applied for all four corners.
+ */
+/*inline*/ fun CutCornerShape(corner: CornerSize) = CutCornerShape(corner, corner, corner, corner)
+
+/**
+ * Creates [CutCornerShape] with the same size applied for all four corners.
+ */
+/*inline*/ fun CutCornerShape(size: Dp) = CutCornerShape(CornerSize(size))
+
+/**
+ * Creates [CutCornerShape] with the same size applied for all four corners.
+ */
+/*inline*/ fun CutCornerShape(size: Px) = CutCornerShape(CornerSize(size))
+
+/**
+ * Creates [CutCornerShape] with the same size applied for all four corners.
+ */
+/*inline*/ fun CutCornerShape(percent: Int) = CutCornerShape(CornerSize(percent))
+
+/**
+ * Creates [CutCornerShape] with sizes defined by [Dp].
+ */
+/*inline*/ fun CutCornerShape(
+ topLeft: Dp = 0.dp,
+ topRight: Dp = 0.dp,
+ bottomRight: Dp = 0.dp,
+ bottomLeft: Dp = 0.dp
+) = CutCornerShape(
+ CornerSize(topLeft),
+ CornerSize(topRight),
+ CornerSize(bottomRight),
+ CornerSize(bottomLeft)
+)
+
+/**
+ * Creates [CutCornerShape] with sizes defined by [Px].
+ */
+/*inline*/ fun CutCornerShape(
+ topLeft: Px = 0.px,
+ topRight: Px = 0.px,
+ bottomRight: Px = 0.px,
+ bottomLeft: Px = 0.px
+) = CutCornerShape(
+ CornerSize(topLeft),
+ CornerSize(topRight),
+ CornerSize(bottomRight),
+ CornerSize(bottomLeft)
+)
+
+/**
+ * Creates [CutCornerShape] with sizes defined by percents of the shape's smaller side.
+ */
+/*inline*/ fun CutCornerShape(
+ @IntRange(from = 0, to = 50) topLeftPercent: Int = 0,
+ @IntRange(from = 0, to = 50) topRightPercent: Int = 0,
+ @IntRange(from = 0, to = 50) bottomRightPercent: Int = 0,
+ @IntRange(from = 0, to = 50) bottomLeftPercent: Int = 0
+) = CutCornerShape(
+ CornerSize(topLeftPercent),
+ CornerSize(topRightPercent),
+ CornerSize(bottomRightPercent),
+ CornerSize(bottomLeftPercent)
+)
diff --git a/ui/ui-material/src/main/java/androidx/ui/material/surface/Card.kt b/ui/ui-material/src/main/java/androidx/ui/material/surface/Card.kt
index 544666b..8ec7d6a 100644
--- a/ui/ui-material/src/main/java/androidx/ui/material/surface/Card.kt
+++ b/ui/ui-material/src/main/java/androidx/ui/material/surface/Card.kt
@@ -16,7 +16,6 @@
package androidx.ui.material.surface
-import androidx.compose.Children
import androidx.compose.Composable
import androidx.compose.composer
import androidx.compose.unaryPlus
@@ -48,12 +47,12 @@
shape: Shape = RectangleShape, // TODO (Andrey: Take the default shape from the theme)
color: Color = +themeColor { surface },
border: Border? = null,
- elevation: Dp = 0.dp,
+ elevation: Dp = 1.dp,
children: @Composable() () -> Unit
) {
// TODO(Andrey: This currently adds no logic on top of Surface, I just reserve the name
// for now. We will see what will be the additional Card specific logic later.
- // It will add the default shape with rounded corners, default 1px elevation, elevate on hover.
+ // It will add the default shape with rounded corners, elevate on hover.
Surface(
shape = shape,
color = color,
diff --git a/ui/ui-material/src/main/java/androidx/ui/material/surface/Surface.kt b/ui/ui-material/src/main/java/androidx/ui/material/surface/Surface.kt
index c63e825..ebb427e 100644
--- a/ui/ui-material/src/main/java/androidx/ui/material/surface/Surface.kt
+++ b/ui/ui-material/src/main/java/androidx/ui/material/surface/Surface.kt
@@ -112,7 +112,7 @@
*/
@Composable
private fun SurfaceLayout(children: @Composable() () -> Unit) {
- Layout(children = children, layoutBlock = { measurables, constraints ->
+ Layout(children) { measurables, constraints ->
if (measurables.size > 1) {
throw IllegalStateException("Surface can have only one direct measurable child!")
}
@@ -125,5 +125,5 @@
placeable.place(0.ipx, 0.ipx)
}
}
- })
+ }
}
diff --git a/ui/ui-platform/api/1.0.0-alpha01.txt b/ui/ui-platform/api/1.0.0-alpha01.txt
index 9a2c483..63c6bd3 100644
--- a/ui/ui-platform/api/1.0.0-alpha01.txt
+++ b/ui/ui-platform/api/1.0.0-alpha01.txt
@@ -73,8 +73,8 @@
method public androidx.ui.core.RepaintBoundaryNode? getRepaintBoundary();
method public final void setDepth(int p);
method public final void setOwnerData(Object? p);
- method public final void visitChildren(kotlin.jvm.functions.Function1<? super androidx.ui.core.ComponentNode,kotlin.Unit> block);
- method public final void visitChildrenReverse(kotlin.jvm.functions.Function1<? super androidx.ui.core.ComponentNode,kotlin.Unit> block);
+ method public final inline void visitChildren(kotlin.jvm.functions.Function1<? super androidx.ui.core.ComponentNode,kotlin.Unit> block);
+ method public final inline void visitChildrenReverse(kotlin.jvm.functions.Function1<? super androidx.ui.core.ComponentNode,kotlin.Unit> block);
property protected androidx.ui.core.LayoutNode? containingLayoutNode;
property public final int count;
property public final int depth;
diff --git a/ui/ui-platform/api/current.txt b/ui/ui-platform/api/current.txt
index 9a2c483..63c6bd3 100644
--- a/ui/ui-platform/api/current.txt
+++ b/ui/ui-platform/api/current.txt
@@ -73,8 +73,8 @@
method public androidx.ui.core.RepaintBoundaryNode? getRepaintBoundary();
method public final void setDepth(int p);
method public final void setOwnerData(Object? p);
- method public final void visitChildren(kotlin.jvm.functions.Function1<? super androidx.ui.core.ComponentNode,kotlin.Unit> block);
- method public final void visitChildrenReverse(kotlin.jvm.functions.Function1<? super androidx.ui.core.ComponentNode,kotlin.Unit> block);
+ method public final inline void visitChildren(kotlin.jvm.functions.Function1<? super androidx.ui.core.ComponentNode,kotlin.Unit> block);
+ method public final inline void visitChildrenReverse(kotlin.jvm.functions.Function1<? super androidx.ui.core.ComponentNode,kotlin.Unit> block);
property protected androidx.ui.core.LayoutNode? containingLayoutNode;
property public final int count;
property public final int depth;
diff --git a/ui/ui-platform/api/restricted_1.0.0-alpha01.txt b/ui/ui-platform/api/restricted_1.0.0-alpha01.txt
index 887803c..58b0117 100644
--- a/ui/ui-platform/api/restricted_1.0.0-alpha01.txt
+++ b/ui/ui-platform/api/restricted_1.0.0-alpha01.txt
@@ -73,8 +73,8 @@
method public androidx.ui.core.RepaintBoundaryNode? getRepaintBoundary();
method public final void setDepth(int p);
method public final void setOwnerData(Object? p);
- method public final void visitChildren(kotlin.jvm.functions.Function1<? super androidx.ui.core.ComponentNode,kotlin.Unit> block);
- method public final void visitChildrenReverse(kotlin.jvm.functions.Function1<? super androidx.ui.core.ComponentNode,kotlin.Unit> block);
+ method public final inline void visitChildren(kotlin.jvm.functions.Function1<? super androidx.ui.core.ComponentNode,kotlin.Unit> block);
+ method public final inline void visitChildrenReverse(kotlin.jvm.functions.Function1<? super androidx.ui.core.ComponentNode,kotlin.Unit> block);
property protected androidx.ui.core.LayoutNode? containingLayoutNode;
property public final int count;
property public final int depth;
diff --git a/ui/ui-platform/api/restricted_current.txt b/ui/ui-platform/api/restricted_current.txt
index 887803c..58b0117 100644
--- a/ui/ui-platform/api/restricted_current.txt
+++ b/ui/ui-platform/api/restricted_current.txt
@@ -73,8 +73,8 @@
method public androidx.ui.core.RepaintBoundaryNode? getRepaintBoundary();
method public final void setDepth(int p);
method public final void setOwnerData(Object? p);
- method public final void visitChildren(kotlin.jvm.functions.Function1<? super androidx.ui.core.ComponentNode,kotlin.Unit> block);
- method public final void visitChildrenReverse(kotlin.jvm.functions.Function1<? super androidx.ui.core.ComponentNode,kotlin.Unit> block);
+ method public final inline void visitChildren(kotlin.jvm.functions.Function1<? super androidx.ui.core.ComponentNode,kotlin.Unit> block);
+ method public final inline void visitChildrenReverse(kotlin.jvm.functions.Function1<? super androidx.ui.core.ComponentNode,kotlin.Unit> block);
property protected androidx.ui.core.LayoutNode? containingLayoutNode;
property public final int count;
property public final int depth;
diff --git a/ui/ui-platform/src/main/java/androidx/ui/core/AndroidOwner.kt b/ui/ui-platform/src/main/java/androidx/ui/core/AndroidOwner.kt
index cce0ade..6c1abe8 100644
--- a/ui/ui-platform/src/main/java/androidx/ui/core/AndroidOwner.kt
+++ b/ui/ui-platform/src/main/java/androidx/ui/core/AndroidOwner.kt
@@ -31,6 +31,7 @@
import android.view.autofill.AutofillValue
import android.view.inputmethod.EditorInfo
import android.view.inputmethod.InputConnection
+import androidx.annotation.RequiresApi
import androidx.annotation.RestrictTo
import androidx.compose.ObserverMap
import androidx.ui.core.input.TextInputServiceAndroid
@@ -424,16 +425,15 @@
currentNode = previousNode
}
is RepaintBoundaryNode -> {
+ val container = node.container
if (node.elevation > 0.dp) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
- canvas.nativeCanvas.enableZ()
- node.container.callDraw(canvas)
- canvas.nativeCanvas.disableZ()
+ container.callDrawWithZApi29(canvas)
} else {
- elevationCompat!!.drawWithZ(canvas, node)
+ elevationCompat!!.drawWithZ(canvas, container)
}
} else {
- node.container.callDraw(canvas)
+ container.callDraw(canvas)
}
}
is LayoutNode -> {
@@ -627,6 +627,16 @@
private fun autofillSupported() = Build.VERSION.SDK_INT >= Build.VERSION_CODES.O
+ @RequiresApi(Build.VERSION_CODES.Q)
+ private fun RepaintBoundary.callDrawWithZApi29(canvas: Canvas) {
+ // do not inline this method as it will decrease the performance. enableZ/disableZ
+ // were added in Q and referencing them in a code not targeting Q makes this code
+ // slower even if the methods will not be executed.
+ canvas.nativeCanvas.enableZ()
+ callDraw(canvas)
+ canvas.nativeCanvas.disableZ()
+ }
+
/**
* Compatible version of Canvas.enableZ()/disableZ().
*
@@ -648,13 +658,13 @@
private inner class ElevationCompat {
private val fakeChild: View = View(context).apply {
setWillNotDraw(true)
- addView(this)
+ this@AndroidCraneView.addView(this)
}
private var fakeDrawPass: Boolean = false
private var boundary: RepaintBoundary? = null
- fun drawWithZ(canvas: Canvas, node: RepaintBoundaryNode) {
- this.boundary = node.container
+ fun drawWithZ(canvas: Canvas, boundary: RepaintBoundary) {
+ this.boundary = boundary
fakeDrawPass = true
super@AndroidCraneView.dispatchDraw(canvas.nativeCanvas)
fakeDrawPass = false
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 2833eb8..1ce1942 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
@@ -164,18 +164,18 @@
/**
* Execute [block] on all children of this ComponentNode.
*/
- fun visitChildren(block: (ComponentNode) -> Unit) {
- for (i in 0 until children.size) {
- block(children[i])
+ inline fun visitChildren(block: (ComponentNode) -> Unit) {
+ for (i in 0 until count) {
+ block(this[i])
}
}
/**
* Execute [block] on all children of this ComponentNode in reverse order.
*/
- fun visitChildrenReverse(block: (ComponentNode) -> Unit) {
- for (i in children.size - 1 downTo 0) {
- block(children[i])
+ inline fun visitChildrenReverse(block: (ComponentNode) -> Unit) {
+ for (i in count - 1 downTo 0) {
+ block(this[i])
}
}
diff --git a/ui/ui-test/api/1.0.0-alpha01.txt b/ui/ui-test/api/1.0.0-alpha01.txt
index 9fd8bf1..3d54f9f 100644
--- a/ui/ui-test/api/1.0.0-alpha01.txt
+++ b/ui/ui-test/api/1.0.0-alpha01.txt
@@ -10,6 +10,8 @@
ctor public AssertionsKt();
method public static java.util.List<androidx.ui.test.SemanticsNodeInteraction> assertCountEquals(java.util.List<androidx.ui.test.SemanticsNodeInteraction>, int count);
method public static void assertDoesNotExist(kotlin.jvm.functions.Function1<? super androidx.ui.core.semantics.SemanticsConfiguration,java.lang.Boolean> selector);
+ method public static androidx.ui.test.SemanticsNodeInteraction assertHasClickAction(androidx.ui.test.SemanticsNodeInteraction);
+ method public static androidx.ui.test.SemanticsNodeInteraction assertHasNoClickAction(androidx.ui.test.SemanticsNodeInteraction);
method public static androidx.ui.test.SemanticsNodeInteraction assertIsChecked(androidx.ui.test.SemanticsNodeInteraction);
method public static androidx.ui.test.SemanticsNodeInteraction assertIsHidden(androidx.ui.test.SemanticsNodeInteraction);
method public static androidx.ui.test.SemanticsNodeInteraction assertIsInMutuallyExclusiveGroup(androidx.ui.test.SemanticsNodeInteraction);
@@ -19,7 +21,6 @@
method public static androidx.ui.test.SemanticsNodeInteraction assertIsVisible(androidx.ui.test.SemanticsNodeInteraction);
method public static void assertNoLongerExists(androidx.ui.test.SemanticsNodeInteraction);
method public static androidx.ui.test.SemanticsNodeInteraction assertSemanticsIsEqualTo(androidx.ui.test.SemanticsNodeInteraction, androidx.ui.core.semantics.SemanticsConfiguration expectedProperties);
- method public static void assertStillExists(androidx.ui.test.SemanticsNodeInteraction);
method public static androidx.ui.test.SemanticsNodeInteraction assertValueEquals(androidx.ui.test.SemanticsNodeInteraction, String value);
method public static void verify(androidx.ui.test.SemanticsNodeInteraction, kotlin.jvm.functions.Function1<? super androidx.ui.core.semantics.SemanticsConfiguration,java.lang.String> assertionMessage, kotlin.jvm.functions.Function1<? super androidx.ui.core.semantics.SemanticsConfiguration,java.lang.Boolean> condition);
}
@@ -60,6 +61,7 @@
public final class FiltersKt {
ctor public FiltersKt();
+ method public static boolean getHasClickAction(androidx.ui.core.semantics.SemanticsConfiguration);
method public static boolean isInMutuallyExclusiveGroup(androidx.ui.core.semantics.SemanticsConfiguration);
method public static boolean isToggleable(androidx.ui.core.semantics.SemanticsConfiguration);
}
diff --git a/ui/ui-test/api/current.txt b/ui/ui-test/api/current.txt
index 9fd8bf1..3d54f9f 100644
--- a/ui/ui-test/api/current.txt
+++ b/ui/ui-test/api/current.txt
@@ -10,6 +10,8 @@
ctor public AssertionsKt();
method public static java.util.List<androidx.ui.test.SemanticsNodeInteraction> assertCountEquals(java.util.List<androidx.ui.test.SemanticsNodeInteraction>, int count);
method public static void assertDoesNotExist(kotlin.jvm.functions.Function1<? super androidx.ui.core.semantics.SemanticsConfiguration,java.lang.Boolean> selector);
+ method public static androidx.ui.test.SemanticsNodeInteraction assertHasClickAction(androidx.ui.test.SemanticsNodeInteraction);
+ method public static androidx.ui.test.SemanticsNodeInteraction assertHasNoClickAction(androidx.ui.test.SemanticsNodeInteraction);
method public static androidx.ui.test.SemanticsNodeInteraction assertIsChecked(androidx.ui.test.SemanticsNodeInteraction);
method public static androidx.ui.test.SemanticsNodeInteraction assertIsHidden(androidx.ui.test.SemanticsNodeInteraction);
method public static androidx.ui.test.SemanticsNodeInteraction assertIsInMutuallyExclusiveGroup(androidx.ui.test.SemanticsNodeInteraction);
@@ -19,7 +21,6 @@
method public static androidx.ui.test.SemanticsNodeInteraction assertIsVisible(androidx.ui.test.SemanticsNodeInteraction);
method public static void assertNoLongerExists(androidx.ui.test.SemanticsNodeInteraction);
method public static androidx.ui.test.SemanticsNodeInteraction assertSemanticsIsEqualTo(androidx.ui.test.SemanticsNodeInteraction, androidx.ui.core.semantics.SemanticsConfiguration expectedProperties);
- method public static void assertStillExists(androidx.ui.test.SemanticsNodeInteraction);
method public static androidx.ui.test.SemanticsNodeInteraction assertValueEquals(androidx.ui.test.SemanticsNodeInteraction, String value);
method public static void verify(androidx.ui.test.SemanticsNodeInteraction, kotlin.jvm.functions.Function1<? super androidx.ui.core.semantics.SemanticsConfiguration,java.lang.String> assertionMessage, kotlin.jvm.functions.Function1<? super androidx.ui.core.semantics.SemanticsConfiguration,java.lang.Boolean> condition);
}
@@ -60,6 +61,7 @@
public final class FiltersKt {
ctor public FiltersKt();
+ method public static boolean getHasClickAction(androidx.ui.core.semantics.SemanticsConfiguration);
method public static boolean isInMutuallyExclusiveGroup(androidx.ui.core.semantics.SemanticsConfiguration);
method public static boolean isToggleable(androidx.ui.core.semantics.SemanticsConfiguration);
}
diff --git a/ui/ui-test/api/restricted_1.0.0-alpha01.txt b/ui/ui-test/api/restricted_1.0.0-alpha01.txt
index 9fd8bf1..3d54f9f 100644
--- a/ui/ui-test/api/restricted_1.0.0-alpha01.txt
+++ b/ui/ui-test/api/restricted_1.0.0-alpha01.txt
@@ -10,6 +10,8 @@
ctor public AssertionsKt();
method public static java.util.List<androidx.ui.test.SemanticsNodeInteraction> assertCountEquals(java.util.List<androidx.ui.test.SemanticsNodeInteraction>, int count);
method public static void assertDoesNotExist(kotlin.jvm.functions.Function1<? super androidx.ui.core.semantics.SemanticsConfiguration,java.lang.Boolean> selector);
+ method public static androidx.ui.test.SemanticsNodeInteraction assertHasClickAction(androidx.ui.test.SemanticsNodeInteraction);
+ method public static androidx.ui.test.SemanticsNodeInteraction assertHasNoClickAction(androidx.ui.test.SemanticsNodeInteraction);
method public static androidx.ui.test.SemanticsNodeInteraction assertIsChecked(androidx.ui.test.SemanticsNodeInteraction);
method public static androidx.ui.test.SemanticsNodeInteraction assertIsHidden(androidx.ui.test.SemanticsNodeInteraction);
method public static androidx.ui.test.SemanticsNodeInteraction assertIsInMutuallyExclusiveGroup(androidx.ui.test.SemanticsNodeInteraction);
@@ -19,7 +21,6 @@
method public static androidx.ui.test.SemanticsNodeInteraction assertIsVisible(androidx.ui.test.SemanticsNodeInteraction);
method public static void assertNoLongerExists(androidx.ui.test.SemanticsNodeInteraction);
method public static androidx.ui.test.SemanticsNodeInteraction assertSemanticsIsEqualTo(androidx.ui.test.SemanticsNodeInteraction, androidx.ui.core.semantics.SemanticsConfiguration expectedProperties);
- method public static void assertStillExists(androidx.ui.test.SemanticsNodeInteraction);
method public static androidx.ui.test.SemanticsNodeInteraction assertValueEquals(androidx.ui.test.SemanticsNodeInteraction, String value);
method public static void verify(androidx.ui.test.SemanticsNodeInteraction, kotlin.jvm.functions.Function1<? super androidx.ui.core.semantics.SemanticsConfiguration,java.lang.String> assertionMessage, kotlin.jvm.functions.Function1<? super androidx.ui.core.semantics.SemanticsConfiguration,java.lang.Boolean> condition);
}
@@ -60,6 +61,7 @@
public final class FiltersKt {
ctor public FiltersKt();
+ method public static boolean getHasClickAction(androidx.ui.core.semantics.SemanticsConfiguration);
method public static boolean isInMutuallyExclusiveGroup(androidx.ui.core.semantics.SemanticsConfiguration);
method public static boolean isToggleable(androidx.ui.core.semantics.SemanticsConfiguration);
}
diff --git a/ui/ui-test/api/restricted_current.txt b/ui/ui-test/api/restricted_current.txt
index 9fd8bf1..3d54f9f 100644
--- a/ui/ui-test/api/restricted_current.txt
+++ b/ui/ui-test/api/restricted_current.txt
@@ -10,6 +10,8 @@
ctor public AssertionsKt();
method public static java.util.List<androidx.ui.test.SemanticsNodeInteraction> assertCountEquals(java.util.List<androidx.ui.test.SemanticsNodeInteraction>, int count);
method public static void assertDoesNotExist(kotlin.jvm.functions.Function1<? super androidx.ui.core.semantics.SemanticsConfiguration,java.lang.Boolean> selector);
+ method public static androidx.ui.test.SemanticsNodeInteraction assertHasClickAction(androidx.ui.test.SemanticsNodeInteraction);
+ method public static androidx.ui.test.SemanticsNodeInteraction assertHasNoClickAction(androidx.ui.test.SemanticsNodeInteraction);
method public static androidx.ui.test.SemanticsNodeInteraction assertIsChecked(androidx.ui.test.SemanticsNodeInteraction);
method public static androidx.ui.test.SemanticsNodeInteraction assertIsHidden(androidx.ui.test.SemanticsNodeInteraction);
method public static androidx.ui.test.SemanticsNodeInteraction assertIsInMutuallyExclusiveGroup(androidx.ui.test.SemanticsNodeInteraction);
@@ -19,7 +21,6 @@
method public static androidx.ui.test.SemanticsNodeInteraction assertIsVisible(androidx.ui.test.SemanticsNodeInteraction);
method public static void assertNoLongerExists(androidx.ui.test.SemanticsNodeInteraction);
method public static androidx.ui.test.SemanticsNodeInteraction assertSemanticsIsEqualTo(androidx.ui.test.SemanticsNodeInteraction, androidx.ui.core.semantics.SemanticsConfiguration expectedProperties);
- method public static void assertStillExists(androidx.ui.test.SemanticsNodeInteraction);
method public static androidx.ui.test.SemanticsNodeInteraction assertValueEquals(androidx.ui.test.SemanticsNodeInteraction, String value);
method public static void verify(androidx.ui.test.SemanticsNodeInteraction, kotlin.jvm.functions.Function1<? super androidx.ui.core.semantics.SemanticsConfiguration,java.lang.String> assertionMessage, kotlin.jvm.functions.Function1<? super androidx.ui.core.semantics.SemanticsConfiguration,java.lang.Boolean> condition);
}
@@ -60,6 +61,7 @@
public final class FiltersKt {
ctor public FiltersKt();
+ method public static boolean getHasClickAction(androidx.ui.core.semantics.SemanticsConfiguration);
method public static boolean isInMutuallyExclusiveGroup(androidx.ui.core.semantics.SemanticsConfiguration);
method public static boolean isToggleable(androidx.ui.core.semantics.SemanticsConfiguration);
}
diff --git a/ui/ui-test/src/main/java/androidx/ui/test/Actions.kt b/ui/ui-test/src/main/java/androidx/ui/test/Actions.kt
index 08ab6eb..2da48e4 100644
--- a/ui/ui-test/src/main/java/androidx/ui/test/Actions.kt
+++ b/ui/ui-test/src/main/java/androidx/ui/test/Actions.kt
@@ -20,7 +20,8 @@
* Performs a click action on the given component.
*/
fun SemanticsNodeInteraction.doClick(): SemanticsNodeInteraction {
- assertStillExists()
+ // TODO(b/129400818): uncomment this after Merge Semantics is merged
+ // assertHasClickAction()
// TODO(catalintudor): get real coordinates after Semantics API is ready (b/125702443)
val globalCoordinates = semanticsTreeNode.globalPosition
diff --git a/ui/ui-test/src/main/java/androidx/ui/test/Assertions.kt b/ui/ui-test/src/main/java/androidx/ui/test/Assertions.kt
index 7f0debd..e417c4d3 100644
--- a/ui/ui-test/src/main/java/androidx/ui/test/Assertions.kt
+++ b/ui/ui-test/src/main/java/androidx/ui/test/Assertions.kt
@@ -62,15 +62,6 @@
}
/**
- * Asserts that the component is still part of the component tree.
- */
-fun SemanticsNodeInteraction.assertStillExists() {
- if (!semanticsTreeInteraction.contains(semanticsTreeNode.data)) {
- throw AssertionError("Assert failed: The component does not exist!")
- }
-}
-
-/**
* Asserts that the component isn't part of the component tree anymore. If the component exists but
* is hidden use [assertIsHidden] instead.
*/
@@ -86,10 +77,9 @@
* Throws [AssertionError] if the component is not unchecked, indeterminate, or not toggleable.
*/
fun SemanticsNodeInteraction.assertIsChecked(): SemanticsNodeInteraction {
+ assertIsToggleable()
verify({ "Component is toggled off, expected it to be toggled on" }) {
- it.getOrElse(FoundationSemanticsProperties.ToggleableState) {
- throw AssertionError("Component is not toggleable")
- } == ToggleableState.Checked
+ it[FoundationSemanticsProperties.ToggleableState] == ToggleableState.Checked
}
return this
}
@@ -100,10 +90,9 @@
* Throws [AssertionError] if the component is checked, indeterminate, or not toggleable.
*/
fun SemanticsNodeInteraction.assertIsUnchecked(): SemanticsNodeInteraction {
+ assertIsToggleable()
verify({ "Component is toggled on, expected it to be toggled off" }) {
- it.getOrElse(FoundationSemanticsProperties.ToggleableState) {
- throw AssertionError("Component is not toggleable")
- } == ToggleableState.Unchecked
+ it[FoundationSemanticsProperties.ToggleableState] == ToggleableState.Unchecked
}
return this
@@ -115,10 +104,10 @@
* Throws [AssertionError] if the component is unselected or not selectable.
*/
fun SemanticsNodeInteraction.assertIsSelected(): SemanticsNodeInteraction {
+ assertIsSelectable()
+
verify({ "Component is unselected, expected it to be selected" }) {
- it.getOrElse(FoundationSemanticsProperties.Selected) {
- throw AssertionError("Component is not selectable")
- }
+ it[FoundationSemanticsProperties.Selected]
}
return this
}
@@ -129,10 +118,10 @@
* Throws [AssertionError] if the component is selected or not selectable.
*/
fun SemanticsNodeInteraction.assertIsUnselected(): SemanticsNodeInteraction {
+ assertIsSelectable()
+
verify({ "Component is selected, expected it to be unselected" }) {
- !it.getOrElse(FoundationSemanticsProperties.Selected) {
- throw AssertionError("Component is not selectable")
- }
+ !it[FoundationSemanticsProperties.Selected]
}
return this
}
@@ -173,14 +162,40 @@
fun SemanticsNodeInteraction.assertSemanticsIsEqualTo(
expectedProperties: SemanticsConfiguration
): SemanticsNodeInteraction {
- assertStillExists()
+ assertExists()
semanticsTreeNode.data.assertEquals(expectedProperties)
return this
}
/**
- * Asserts that given a list of components, it's size is equal to the passed in size.
+ * Asserts that the current component has a click action.
+ *
+ * Throws [AssertionError] if the component is doesn't have a click action.
+ */
+fun SemanticsNodeInteraction.assertHasClickAction(): SemanticsNodeInteraction {
+ verify({ "Component is not clickable, expected it to be clickable" }) {
+ it.hasClickAction
+ }
+
+ return this
+}
+
+/**
+ * Asserts that the current component doesn't have a click action.
+ *
+ * Throws [AssertionError] if the component has a click action.
+ */
+fun SemanticsNodeInteraction.assertHasNoClickAction(): SemanticsNodeInteraction {
+ verify({ "Component is clickable, expected it to not be clickable" }) {
+ !it.hasClickAction
+ }
+
+ return this
+}
+
+/**
+ * Asserts that given a list of components, its size is equal to the passed in size.
*/
fun List<SemanticsNodeInteraction>.assertCountEquals(
count: Int
@@ -201,10 +216,45 @@
assertionMessage: (SemanticsConfiguration) -> String,
condition: (SemanticsConfiguration) -> Boolean
) {
- assertStillExists()
+ assertExists()
if (!condition.invoke(semanticsTreeNode.data)) {
// TODO(b/133217292)
throw AssertionError("Assert failed: ${assertionMessage(semanticsTreeNode.data)}")
}
}
+
+/**
+ * Asserts that the component is still part of the component tree.
+ */
+internal fun SemanticsNodeInteraction.assertExists() {
+ if (!semanticsTreeInteraction.contains(semanticsTreeNode.data)) {
+ throw AssertionError("The component does not exist!")
+ }
+}
+
+/**
+ * Asserts that the current component is toggleable.
+ *
+ * Throws [AssertionError] if the component is not toggleable.
+ */
+internal fun SemanticsNodeInteraction.assertIsToggleable(): SemanticsNodeInteraction {
+ verify({ "Component is not toggleable, expected it to be toggleable" }) {
+ it.isToggleable
+ }
+
+ return this
+}
+
+/**
+ * Asserts that the current component is selectable.
+ *
+ * Throws [AssertionError] if the component is not selectable.
+ */
+internal fun SemanticsNodeInteraction.assertIsSelectable(): SemanticsNodeInteraction {
+ verify({ "Component is not selectable, expected it to be selectable" }) {
+ it.getOrNull(FoundationSemanticsProperties.Selected) != null
+ }
+
+ return this
+}
\ No newline at end of file
diff --git a/ui/ui-test/src/main/java/androidx/ui/test/Filters.kt b/ui/ui-test/src/main/java/androidx/ui/test/Filters.kt
index 3f3d8cb..3827927 100644
--- a/ui/ui-test/src/main/java/androidx/ui/test/Filters.kt
+++ b/ui/ui-test/src/main/java/androidx/ui/test/Filters.kt
@@ -19,6 +19,7 @@
import androidx.ui.core.semantics.SemanticsConfiguration
import androidx.ui.core.semantics.getOrNull
import androidx.ui.foundation.semantics.FoundationSemanticsProperties
+import androidx.ui.semantics.SemanticsActions
/**
* Verifies that a component is checkable.
@@ -26,6 +27,9 @@
val SemanticsConfiguration.isToggleable: Boolean
get() = contains(FoundationSemanticsProperties.ToggleableState)
+val SemanticsConfiguration.hasClickAction: Boolean
+ get() = SemanticsActions.OnClick in this
+
// TODO(ryanmentley/pavlis): Do we want these convenience functions?
/**
* Verifies that a component is in a mutually exclusive group - that is,
diff --git a/ui/ui-test/src/test/java/androidx/ui/test/FindersTests.kt b/ui/ui-test/src/test/java/androidx/ui/test/FindersTests.kt
index deb6f9c..c487f67 100644
--- a/ui/ui-test/src/test/java/androidx/ui/test/FindersTests.kt
+++ b/ui/ui-test/src/test/java/androidx/ui/test/FindersTests.kt
@@ -25,10 +25,9 @@
import com.google.common.truth.Truth
import org.junit.Test
-// TODO(b/138167927): findByTag_* tests do not call findByTag
class FindersTests {
@Test
- fun findByTag_zeroOutOfOne_findsNone() {
+ fun findAll_zeroOutOfOne_findsNone() {
semanticsTreeInteractionFactory = { selector ->
FakeSemanticsTreeInteraction(selector)
.withSemantics(newNode(
@@ -43,7 +42,7 @@
}
@Test
- fun findByTag_oneOutOfTwo_findsOne() {
+ fun findAll_oneOutOfTwo_findsOne() {
val node1 = newNode(SemanticsConfiguration().apply {
testTag = "myTestTag"
})
@@ -61,7 +60,7 @@
}
@Test
- fun findByTag_twoOutOfTwo_findsTwo() {
+ fun findAll_twoOutOfTwo_findsTwo() {
val node1 = newNode(
SemanticsConfiguration().apply {
testTag = "myTestTag"
diff --git a/ui/ui-text/api/1.0.0-alpha01.txt b/ui/ui-text/api/1.0.0-alpha01.txt
index 11033b4..cab98ee 100644
--- a/ui/ui-text/api/1.0.0-alpha01.txt
+++ b/ui/ui-text/api/1.0.0-alpha01.txt
@@ -11,6 +11,18 @@
property public final String text;
}
+ public final class EditorStyle {
+ ctor public EditorStyle(androidx.ui.text.TextStyle? textStyle, androidx.ui.graphics.Color compositionColor, androidx.ui.graphics.Color selectionColor);
+ ctor public EditorStyle();
+ method public androidx.ui.text.TextStyle? component1();
+ method public androidx.ui.graphics.Color component2();
+ method public androidx.ui.graphics.Color component3();
+ method public androidx.ui.input.EditorStyle copy(androidx.ui.text.TextStyle? textStyle, androidx.ui.graphics.Color compositionColor, androidx.ui.graphics.Color selectionColor);
+ method public androidx.ui.graphics.Color getCompositionColor();
+ method public androidx.ui.graphics.Color getSelectionColor();
+ method public androidx.ui.text.TextStyle? getTextStyle();
+ }
+
public enum ImeAction {
enum_constant public static final androidx.ui.input.ImeAction Done;
enum_constant public static final androidx.ui.input.ImeAction Go;
@@ -33,6 +45,35 @@
enum_constant public static final androidx.ui.input.KeyboardType Uri;
}
+ public interface OffsetMap {
+ method public int originalToTransformed(int offset);
+ method public int transformedToOriginal(int offset);
+ }
+
+ public final class PasswordVisualTransformation implements androidx.ui.input.VisualTransformation {
+ ctor public PasswordVisualTransformation(char mask);
+ ctor public PasswordVisualTransformation();
+ method public androidx.ui.input.TransformedText filter(androidx.ui.text.AnnotatedString text);
+ method public char getMask();
+ }
+
+ public final class TransformedText {
+ ctor public TransformedText(androidx.ui.text.AnnotatedString transformedText, androidx.ui.input.OffsetMap offsetMap);
+ method public androidx.ui.text.AnnotatedString component1();
+ method public androidx.ui.input.OffsetMap component2();
+ method public androidx.ui.input.TransformedText copy(androidx.ui.text.AnnotatedString transformedText, androidx.ui.input.OffsetMap offsetMap);
+ method public androidx.ui.input.OffsetMap getOffsetMap();
+ method public androidx.ui.text.AnnotatedString getTransformedText();
+ }
+
+ public interface VisualTransformation {
+ method public androidx.ui.input.TransformedText filter(androidx.ui.text.AnnotatedString text);
+ }
+
+ public final class VisualTransformationKt {
+ ctor public VisualTransformationKt();
+ }
+
}
package androidx.ui.text {
@@ -63,31 +104,13 @@
method public T! getStyle();
}
- public final class Locale {
- ctor public Locale(String _languageCode, String? _countryCode);
- method public String component1();
- method public String? component2();
- method public androidx.ui.text.Locale copy(String _languageCode, String? _countryCode);
- method public String! getCountryCode();
- method public String getLanguageCode();
- method public String? get_countryCode();
- method public String get_languageCode();
- property public final String! countryCode;
- property public final String languageCode;
- field public static final androidx.ui.text.Locale.Companion! Companion;
- }
-
- public static final class Locale.Companion {
- method public String _canonicalizeLanguageCode(String languageCode);
- method public String _canonicalizeRegionCode(String regionCode);
- }
-
public final class MultiParagraphKt {
ctor public MultiParagraphKt();
}
public interface Paragraph {
method public float getBaseline();
+ method public androidx.ui.text.style.TextDirection getBidiRunDirection(int offset);
method public androidx.ui.engine.geometry.Rect getCursorRect(int offset);
method public boolean getDidExceedMaxLines();
method public float getHeight();
@@ -99,6 +122,7 @@
method public float getMaxIntrinsicWidth();
method public float getMinIntrinsicWidth();
method public int getOffsetForPosition(androidx.ui.core.PxPosition position);
+ method public androidx.ui.text.style.TextDirection getParagraphDirection(int offset);
method public androidx.ui.painting.Path getPathForRange(int start, int end);
method public float getWidth();
method public androidx.ui.text.TextRange getWordBoundary(int offset);
@@ -140,43 +164,8 @@
method public androidx.ui.text.ParagraphStyle merge(androidx.ui.text.ParagraphStyle? other = null);
}
- public final class TextPainter {
- ctor public TextPainter(androidx.ui.text.AnnotatedString? text, androidx.ui.text.TextStyle? style, androidx.ui.text.ParagraphStyle? paragraphStyle, Integer? maxLines, boolean softWrap, androidx.ui.text.style.TextOverflow overflow, androidx.ui.text.Locale? locale, androidx.ui.core.Density density, androidx.ui.text.font.Font.ResourceLoader resourceLoader);
- method public androidx.ui.core.Density getDensity();
- method public boolean getDidExceedMaxLines();
- method public float getHeight();
- method public androidx.ui.text.Locale? getLocale();
- method public float getMaxIntrinsicWidth();
- method public Integer? getMaxLines();
- method public float getMinIntrinsicWidth();
- method public int getOffsetForPosition(androidx.ui.core.PxPosition position);
- method public androidx.ui.text.style.TextOverflow getOverflow();
- method public androidx.ui.text.ParagraphStyle? getParagraphStyle();
- method public float getPreferredLineHeight();
- method public androidx.ui.text.font.Font.ResourceLoader getResourceLoader();
- method public androidx.ui.engine.geometry.Size getSize();
- method public boolean getSoftWrap();
- method public androidx.ui.text.TextStyle? getStyle();
- method public androidx.ui.text.AnnotatedString? getText();
- method public float getWidth();
- method public androidx.ui.text.TextRange getWordBoundary(int offset);
- method public void layout(androidx.ui.core.Constraints constraints);
- method public void paint(androidx.ui.painting.Canvas canvas);
- method public void paintBackground(int start, int end, androidx.ui.graphics.Color color, androidx.ui.painting.Canvas canvas);
- method public void paintCursor(int offset, androidx.ui.painting.Canvas canvas);
- method public void setText(androidx.ui.text.AnnotatedString? value);
- property public final boolean didExceedMaxLines;
- property public final float height;
- property public final float maxIntrinsicWidth;
- property public final float minIntrinsicWidth;
- property public final float preferredLineHeight;
- property public final androidx.ui.engine.geometry.Size size;
- property public final androidx.ui.text.AnnotatedString? text;
- property public final float width;
- }
-
- public final class TextPainterKt {
- ctor public TextPainterKt();
+ public final class TextDelegateKt {
+ ctor public TextDelegateKt();
}
public final class TextSpanKt {
@@ -184,12 +173,12 @@
}
public final class TextStyle {
- ctor public TextStyle(androidx.ui.graphics.Color? color, androidx.ui.core.Sp? fontSize, Float? fontSizeScale, androidx.ui.text.font.FontWeight? fontWeight, androidx.ui.text.font.FontStyle? fontStyle, androidx.ui.text.font.FontSynthesis? fontSynthesis, androidx.ui.text.font.FontFamily? fontFamily, String? fontFeatureSettings, Float? letterSpacing, androidx.ui.text.style.BaselineShift? baselineShift, androidx.ui.text.style.TextGeometricTransform? textGeometricTransform, androidx.ui.text.Locale? locale, androidx.ui.graphics.Color? background, androidx.ui.text.style.TextDecoration? decoration, androidx.ui.painting.Shadow? shadow);
+ ctor public TextStyle(androidx.ui.graphics.Color? color, androidx.ui.core.Sp? fontSize, Float? fontSizeScale, androidx.ui.text.font.FontWeight? fontWeight, androidx.ui.text.font.FontStyle? fontStyle, androidx.ui.text.font.FontSynthesis? fontSynthesis, androidx.ui.text.font.FontFamily? fontFamily, String? fontFeatureSettings, Float? letterSpacing, androidx.ui.text.style.BaselineShift? baselineShift, androidx.ui.text.style.TextGeometricTransform? textGeometricTransform, java.util.Locale? locale, androidx.ui.graphics.Color? background, androidx.ui.text.style.TextDecoration? decoration, androidx.ui.painting.Shadow? shadow);
ctor public TextStyle();
method public androidx.ui.graphics.Color? component1();
method public androidx.ui.text.style.BaselineShift? component10();
method public androidx.ui.text.style.TextGeometricTransform? component11();
- method public androidx.ui.text.Locale? component12();
+ method public java.util.Locale? component12();
method public androidx.ui.graphics.Color? component13();
method public androidx.ui.text.style.TextDecoration? component14();
method public androidx.ui.painting.Shadow? component15();
@@ -201,7 +190,7 @@
method public androidx.ui.text.font.FontFamily? component7();
method public String? component8();
method public Float? component9();
- method public androidx.ui.text.TextStyle copy(androidx.ui.graphics.Color? color, androidx.ui.core.Sp? fontSize, Float? fontSizeScale, androidx.ui.text.font.FontWeight? fontWeight, androidx.ui.text.font.FontStyle? fontStyle, androidx.ui.text.font.FontSynthesis? fontSynthesis, androidx.ui.text.font.FontFamily? fontFamily, String? fontFeatureSettings, Float? letterSpacing, androidx.ui.text.style.BaselineShift? baselineShift, androidx.ui.text.style.TextGeometricTransform? textGeometricTransform, androidx.ui.text.Locale? locale, androidx.ui.graphics.Color? background, androidx.ui.text.style.TextDecoration? decoration, androidx.ui.painting.Shadow? shadow);
+ method public androidx.ui.text.TextStyle copy(androidx.ui.graphics.Color? color, androidx.ui.core.Sp? fontSize, Float? fontSizeScale, androidx.ui.text.font.FontWeight? fontWeight, androidx.ui.text.font.FontStyle? fontStyle, androidx.ui.text.font.FontSynthesis? fontSynthesis, androidx.ui.text.font.FontFamily? fontFamily, String? fontFeatureSettings, Float? letterSpacing, androidx.ui.text.style.BaselineShift? baselineShift, androidx.ui.text.style.TextGeometricTransform? textGeometricTransform, java.util.Locale? locale, androidx.ui.graphics.Color? background, androidx.ui.text.style.TextDecoration? decoration, androidx.ui.painting.Shadow? shadow);
method public androidx.ui.graphics.Color? getBackground();
method public androidx.ui.text.style.BaselineShift? getBaselineShift();
method public androidx.ui.graphics.Color? getColor();
@@ -214,7 +203,7 @@
method public androidx.ui.text.font.FontSynthesis? getFontSynthesis();
method public androidx.ui.text.font.FontWeight? getFontWeight();
method public Float? getLetterSpacing();
- method public androidx.ui.text.Locale? getLocale();
+ method public java.util.Locale? getLocale();
method public androidx.ui.painting.Shadow? getShadow();
method public androidx.ui.text.style.TextGeometricTransform? getTextGeometricTransform();
method public androidx.ui.text.TextStyle merge(androidx.ui.text.TextStyle? other = null);
diff --git a/ui/ui-text/api/api_lint.ignore b/ui/ui-text/api/api_lint.ignore
index 6051d58..8d78f05 100644
--- a/ui/ui-text/api/api_lint.ignore
+++ b/ui/ui-text/api/api_lint.ignore
@@ -9,21 +9,17 @@
Must avoid boxed primitives (`java.lang.Float`)
AutoBoxing: androidx.ui.text.ParagraphStyle#getLineHeight():
Must avoid boxed primitives (`java.lang.Float`)
-AutoBoxing: androidx.ui.text.TextPainter#TextPainter(androidx.ui.text.AnnotatedString, androidx.ui.text.TextStyle, androidx.ui.text.ParagraphStyle, Integer, boolean, androidx.ui.text.style.TextOverflow, androidx.ui.text.Locale, androidx.ui.core.Density, androidx.ui.text.font.Font.ResourceLoader) parameter #3:
- Must avoid boxed primitives (`java.lang.Integer`)
-AutoBoxing: androidx.ui.text.TextPainter#getMaxLines():
- Must avoid boxed primitives (`java.lang.Integer`)
-AutoBoxing: androidx.ui.text.TextStyle#TextStyle(androidx.ui.graphics.Color, androidx.ui.core.Sp, Float, androidx.ui.text.font.FontWeight, androidx.ui.text.font.FontStyle, androidx.ui.text.font.FontSynthesis, androidx.ui.text.font.FontFamily, String, Float, androidx.ui.text.style.BaselineShift, androidx.ui.text.style.TextGeometricTransform, androidx.ui.text.Locale, androidx.ui.graphics.Color, androidx.ui.text.style.TextDecoration, androidx.ui.painting.Shadow) parameter #2:
+AutoBoxing: androidx.ui.text.TextStyle#TextStyle(androidx.ui.graphics.Color, androidx.ui.core.Sp, Float, androidx.ui.text.font.FontWeight, androidx.ui.text.font.FontStyle, androidx.ui.text.font.FontSynthesis, androidx.ui.text.font.FontFamily, String, Float, androidx.ui.text.style.BaselineShift, androidx.ui.text.style.TextGeometricTransform, java.util.Locale, androidx.ui.graphics.Color, androidx.ui.text.style.TextDecoration, androidx.ui.painting.Shadow) parameter #2:
Must avoid boxed primitives (`java.lang.Float`)
-AutoBoxing: androidx.ui.text.TextStyle#TextStyle(androidx.ui.graphics.Color, androidx.ui.core.Sp, Float, androidx.ui.text.font.FontWeight, androidx.ui.text.font.FontStyle, androidx.ui.text.font.FontSynthesis, androidx.ui.text.font.FontFamily, String, Float, androidx.ui.text.style.BaselineShift, androidx.ui.text.style.TextGeometricTransform, androidx.ui.text.Locale, androidx.ui.graphics.Color, androidx.ui.text.style.TextDecoration, androidx.ui.painting.Shadow) parameter #8:
+AutoBoxing: androidx.ui.text.TextStyle#TextStyle(androidx.ui.graphics.Color, androidx.ui.core.Sp, Float, androidx.ui.text.font.FontWeight, androidx.ui.text.font.FontStyle, androidx.ui.text.font.FontSynthesis, androidx.ui.text.font.FontFamily, String, Float, androidx.ui.text.style.BaselineShift, androidx.ui.text.style.TextGeometricTransform, java.util.Locale, androidx.ui.graphics.Color, androidx.ui.text.style.TextDecoration, androidx.ui.painting.Shadow) parameter #8:
Must avoid boxed primitives (`java.lang.Float`)
AutoBoxing: androidx.ui.text.TextStyle#component3():
Must avoid boxed primitives (`java.lang.Float`)
AutoBoxing: androidx.ui.text.TextStyle#component9():
Must avoid boxed primitives (`java.lang.Float`)
-AutoBoxing: androidx.ui.text.TextStyle#copy(androidx.ui.graphics.Color, androidx.ui.core.Sp, Float, androidx.ui.text.font.FontWeight, androidx.ui.text.font.FontStyle, androidx.ui.text.font.FontSynthesis, androidx.ui.text.font.FontFamily, String, Float, androidx.ui.text.style.BaselineShift, androidx.ui.text.style.TextGeometricTransform, androidx.ui.text.Locale, androidx.ui.graphics.Color, androidx.ui.text.style.TextDecoration, androidx.ui.painting.Shadow) parameter #2:
+AutoBoxing: androidx.ui.text.TextStyle#copy(androidx.ui.graphics.Color, androidx.ui.core.Sp, Float, androidx.ui.text.font.FontWeight, androidx.ui.text.font.FontStyle, androidx.ui.text.font.FontSynthesis, androidx.ui.text.font.FontFamily, String, Float, androidx.ui.text.style.BaselineShift, androidx.ui.text.style.TextGeometricTransform, java.util.Locale, androidx.ui.graphics.Color, androidx.ui.text.style.TextDecoration, androidx.ui.painting.Shadow) parameter #2:
Must avoid boxed primitives (`java.lang.Float`)
-AutoBoxing: androidx.ui.text.TextStyle#copy(androidx.ui.graphics.Color, androidx.ui.core.Sp, Float, androidx.ui.text.font.FontWeight, androidx.ui.text.font.FontStyle, androidx.ui.text.font.FontSynthesis, androidx.ui.text.font.FontFamily, String, Float, androidx.ui.text.style.BaselineShift, androidx.ui.text.style.TextGeometricTransform, androidx.ui.text.Locale, androidx.ui.graphics.Color, androidx.ui.text.style.TextDecoration, androidx.ui.painting.Shadow) parameter #8:
+AutoBoxing: androidx.ui.text.TextStyle#copy(androidx.ui.graphics.Color, androidx.ui.core.Sp, Float, androidx.ui.text.font.FontWeight, androidx.ui.text.font.FontStyle, androidx.ui.text.font.FontSynthesis, androidx.ui.text.font.FontFamily, String, Float, androidx.ui.text.style.BaselineShift, androidx.ui.text.style.TextGeometricTransform, java.util.Locale, androidx.ui.graphics.Color, androidx.ui.text.style.TextDecoration, androidx.ui.painting.Shadow) parameter #8:
Must avoid boxed primitives (`java.lang.Float`)
AutoBoxing: androidx.ui.text.TextStyle#getFontSizeScale():
Must avoid boxed primitives (`java.lang.Float`)
@@ -55,10 +51,6 @@
Note that adding the `operator` keyword would allow calling this method using operator syntax
-MissingNullability: androidx.ui.text.Locale#Companion:
- Missing nullability on field `Companion` in class `class androidx.ui.text.Locale`
-MissingNullability: androidx.ui.text.Locale#getCountryCode():
- Missing nullability on method `getCountryCode` return
MissingNullability: androidx.ui.text.font.FontWeight#Companion:
Missing nullability on field `Companion` in class `class androidx.ui.text.font.FontWeight`
MissingNullability: androidx.ui.text.style.BaselineShift#Companion:
diff --git a/ui/ui-text/api/current.txt b/ui/ui-text/api/current.txt
index 11033b4..cab98ee 100644
--- a/ui/ui-text/api/current.txt
+++ b/ui/ui-text/api/current.txt
@@ -11,6 +11,18 @@
property public final String text;
}
+ public final class EditorStyle {
+ ctor public EditorStyle(androidx.ui.text.TextStyle? textStyle, androidx.ui.graphics.Color compositionColor, androidx.ui.graphics.Color selectionColor);
+ ctor public EditorStyle();
+ method public androidx.ui.text.TextStyle? component1();
+ method public androidx.ui.graphics.Color component2();
+ method public androidx.ui.graphics.Color component3();
+ method public androidx.ui.input.EditorStyle copy(androidx.ui.text.TextStyle? textStyle, androidx.ui.graphics.Color compositionColor, androidx.ui.graphics.Color selectionColor);
+ method public androidx.ui.graphics.Color getCompositionColor();
+ method public androidx.ui.graphics.Color getSelectionColor();
+ method public androidx.ui.text.TextStyle? getTextStyle();
+ }
+
public enum ImeAction {
enum_constant public static final androidx.ui.input.ImeAction Done;
enum_constant public static final androidx.ui.input.ImeAction Go;
@@ -33,6 +45,35 @@
enum_constant public static final androidx.ui.input.KeyboardType Uri;
}
+ public interface OffsetMap {
+ method public int originalToTransformed(int offset);
+ method public int transformedToOriginal(int offset);
+ }
+
+ public final class PasswordVisualTransformation implements androidx.ui.input.VisualTransformation {
+ ctor public PasswordVisualTransformation(char mask);
+ ctor public PasswordVisualTransformation();
+ method public androidx.ui.input.TransformedText filter(androidx.ui.text.AnnotatedString text);
+ method public char getMask();
+ }
+
+ public final class TransformedText {
+ ctor public TransformedText(androidx.ui.text.AnnotatedString transformedText, androidx.ui.input.OffsetMap offsetMap);
+ method public androidx.ui.text.AnnotatedString component1();
+ method public androidx.ui.input.OffsetMap component2();
+ method public androidx.ui.input.TransformedText copy(androidx.ui.text.AnnotatedString transformedText, androidx.ui.input.OffsetMap offsetMap);
+ method public androidx.ui.input.OffsetMap getOffsetMap();
+ method public androidx.ui.text.AnnotatedString getTransformedText();
+ }
+
+ public interface VisualTransformation {
+ method public androidx.ui.input.TransformedText filter(androidx.ui.text.AnnotatedString text);
+ }
+
+ public final class VisualTransformationKt {
+ ctor public VisualTransformationKt();
+ }
+
}
package androidx.ui.text {
@@ -63,31 +104,13 @@
method public T! getStyle();
}
- public final class Locale {
- ctor public Locale(String _languageCode, String? _countryCode);
- method public String component1();
- method public String? component2();
- method public androidx.ui.text.Locale copy(String _languageCode, String? _countryCode);
- method public String! getCountryCode();
- method public String getLanguageCode();
- method public String? get_countryCode();
- method public String get_languageCode();
- property public final String! countryCode;
- property public final String languageCode;
- field public static final androidx.ui.text.Locale.Companion! Companion;
- }
-
- public static final class Locale.Companion {
- method public String _canonicalizeLanguageCode(String languageCode);
- method public String _canonicalizeRegionCode(String regionCode);
- }
-
public final class MultiParagraphKt {
ctor public MultiParagraphKt();
}
public interface Paragraph {
method public float getBaseline();
+ method public androidx.ui.text.style.TextDirection getBidiRunDirection(int offset);
method public androidx.ui.engine.geometry.Rect getCursorRect(int offset);
method public boolean getDidExceedMaxLines();
method public float getHeight();
@@ -99,6 +122,7 @@
method public float getMaxIntrinsicWidth();
method public float getMinIntrinsicWidth();
method public int getOffsetForPosition(androidx.ui.core.PxPosition position);
+ method public androidx.ui.text.style.TextDirection getParagraphDirection(int offset);
method public androidx.ui.painting.Path getPathForRange(int start, int end);
method public float getWidth();
method public androidx.ui.text.TextRange getWordBoundary(int offset);
@@ -140,43 +164,8 @@
method public androidx.ui.text.ParagraphStyle merge(androidx.ui.text.ParagraphStyle? other = null);
}
- public final class TextPainter {
- ctor public TextPainter(androidx.ui.text.AnnotatedString? text, androidx.ui.text.TextStyle? style, androidx.ui.text.ParagraphStyle? paragraphStyle, Integer? maxLines, boolean softWrap, androidx.ui.text.style.TextOverflow overflow, androidx.ui.text.Locale? locale, androidx.ui.core.Density density, androidx.ui.text.font.Font.ResourceLoader resourceLoader);
- method public androidx.ui.core.Density getDensity();
- method public boolean getDidExceedMaxLines();
- method public float getHeight();
- method public androidx.ui.text.Locale? getLocale();
- method public float getMaxIntrinsicWidth();
- method public Integer? getMaxLines();
- method public float getMinIntrinsicWidth();
- method public int getOffsetForPosition(androidx.ui.core.PxPosition position);
- method public androidx.ui.text.style.TextOverflow getOverflow();
- method public androidx.ui.text.ParagraphStyle? getParagraphStyle();
- method public float getPreferredLineHeight();
- method public androidx.ui.text.font.Font.ResourceLoader getResourceLoader();
- method public androidx.ui.engine.geometry.Size getSize();
- method public boolean getSoftWrap();
- method public androidx.ui.text.TextStyle? getStyle();
- method public androidx.ui.text.AnnotatedString? getText();
- method public float getWidth();
- method public androidx.ui.text.TextRange getWordBoundary(int offset);
- method public void layout(androidx.ui.core.Constraints constraints);
- method public void paint(androidx.ui.painting.Canvas canvas);
- method public void paintBackground(int start, int end, androidx.ui.graphics.Color color, androidx.ui.painting.Canvas canvas);
- method public void paintCursor(int offset, androidx.ui.painting.Canvas canvas);
- method public void setText(androidx.ui.text.AnnotatedString? value);
- property public final boolean didExceedMaxLines;
- property public final float height;
- property public final float maxIntrinsicWidth;
- property public final float minIntrinsicWidth;
- property public final float preferredLineHeight;
- property public final androidx.ui.engine.geometry.Size size;
- property public final androidx.ui.text.AnnotatedString? text;
- property public final float width;
- }
-
- public final class TextPainterKt {
- ctor public TextPainterKt();
+ public final class TextDelegateKt {
+ ctor public TextDelegateKt();
}
public final class TextSpanKt {
@@ -184,12 +173,12 @@
}
public final class TextStyle {
- ctor public TextStyle(androidx.ui.graphics.Color? color, androidx.ui.core.Sp? fontSize, Float? fontSizeScale, androidx.ui.text.font.FontWeight? fontWeight, androidx.ui.text.font.FontStyle? fontStyle, androidx.ui.text.font.FontSynthesis? fontSynthesis, androidx.ui.text.font.FontFamily? fontFamily, String? fontFeatureSettings, Float? letterSpacing, androidx.ui.text.style.BaselineShift? baselineShift, androidx.ui.text.style.TextGeometricTransform? textGeometricTransform, androidx.ui.text.Locale? locale, androidx.ui.graphics.Color? background, androidx.ui.text.style.TextDecoration? decoration, androidx.ui.painting.Shadow? shadow);
+ ctor public TextStyle(androidx.ui.graphics.Color? color, androidx.ui.core.Sp? fontSize, Float? fontSizeScale, androidx.ui.text.font.FontWeight? fontWeight, androidx.ui.text.font.FontStyle? fontStyle, androidx.ui.text.font.FontSynthesis? fontSynthesis, androidx.ui.text.font.FontFamily? fontFamily, String? fontFeatureSettings, Float? letterSpacing, androidx.ui.text.style.BaselineShift? baselineShift, androidx.ui.text.style.TextGeometricTransform? textGeometricTransform, java.util.Locale? locale, androidx.ui.graphics.Color? background, androidx.ui.text.style.TextDecoration? decoration, androidx.ui.painting.Shadow? shadow);
ctor public TextStyle();
method public androidx.ui.graphics.Color? component1();
method public androidx.ui.text.style.BaselineShift? component10();
method public androidx.ui.text.style.TextGeometricTransform? component11();
- method public androidx.ui.text.Locale? component12();
+ method public java.util.Locale? component12();
method public androidx.ui.graphics.Color? component13();
method public androidx.ui.text.style.TextDecoration? component14();
method public androidx.ui.painting.Shadow? component15();
@@ -201,7 +190,7 @@
method public androidx.ui.text.font.FontFamily? component7();
method public String? component8();
method public Float? component9();
- method public androidx.ui.text.TextStyle copy(androidx.ui.graphics.Color? color, androidx.ui.core.Sp? fontSize, Float? fontSizeScale, androidx.ui.text.font.FontWeight? fontWeight, androidx.ui.text.font.FontStyle? fontStyle, androidx.ui.text.font.FontSynthesis? fontSynthesis, androidx.ui.text.font.FontFamily? fontFamily, String? fontFeatureSettings, Float? letterSpacing, androidx.ui.text.style.BaselineShift? baselineShift, androidx.ui.text.style.TextGeometricTransform? textGeometricTransform, androidx.ui.text.Locale? locale, androidx.ui.graphics.Color? background, androidx.ui.text.style.TextDecoration? decoration, androidx.ui.painting.Shadow? shadow);
+ method public androidx.ui.text.TextStyle copy(androidx.ui.graphics.Color? color, androidx.ui.core.Sp? fontSize, Float? fontSizeScale, androidx.ui.text.font.FontWeight? fontWeight, androidx.ui.text.font.FontStyle? fontStyle, androidx.ui.text.font.FontSynthesis? fontSynthesis, androidx.ui.text.font.FontFamily? fontFamily, String? fontFeatureSettings, Float? letterSpacing, androidx.ui.text.style.BaselineShift? baselineShift, androidx.ui.text.style.TextGeometricTransform? textGeometricTransform, java.util.Locale? locale, androidx.ui.graphics.Color? background, androidx.ui.text.style.TextDecoration? decoration, androidx.ui.painting.Shadow? shadow);
method public androidx.ui.graphics.Color? getBackground();
method public androidx.ui.text.style.BaselineShift? getBaselineShift();
method public androidx.ui.graphics.Color? getColor();
@@ -214,7 +203,7 @@
method public androidx.ui.text.font.FontSynthesis? getFontSynthesis();
method public androidx.ui.text.font.FontWeight? getFontWeight();
method public Float? getLetterSpacing();
- method public androidx.ui.text.Locale? getLocale();
+ method public java.util.Locale? getLocale();
method public androidx.ui.painting.Shadow? getShadow();
method public androidx.ui.text.style.TextGeometricTransform? getTextGeometricTransform();
method public androidx.ui.text.TextStyle merge(androidx.ui.text.TextStyle? other = null);
diff --git a/ui/ui-text/api/restricted_1.0.0-alpha01.txt b/ui/ui-text/api/restricted_1.0.0-alpha01.txt
index 4850b0f..2610db4 100644
--- a/ui/ui-text/api/restricted_1.0.0-alpha01.txt
+++ b/ui/ui-text/api/restricted_1.0.0-alpha01.txt
@@ -18,6 +18,18 @@
property public final String text;
}
+ public final class EditorStyle {
+ ctor public EditorStyle(androidx.ui.text.TextStyle? textStyle, androidx.ui.graphics.Color compositionColor, androidx.ui.graphics.Color selectionColor);
+ ctor public EditorStyle();
+ method public androidx.ui.text.TextStyle? component1();
+ method public androidx.ui.graphics.Color component2();
+ method public androidx.ui.graphics.Color component3();
+ method public androidx.ui.input.EditorStyle copy(androidx.ui.text.TextStyle? textStyle, androidx.ui.graphics.Color compositionColor, androidx.ui.graphics.Color selectionColor);
+ method public androidx.ui.graphics.Color getCompositionColor();
+ method public androidx.ui.graphics.Color getSelectionColor();
+ method public androidx.ui.text.TextStyle? getTextStyle();
+ }
+
public enum ImeAction {
enum_constant public static final androidx.ui.input.ImeAction Done;
@@ -43,10 +55,39 @@
}
+ public interface OffsetMap {
+ method public int originalToTransformed(int offset);
+ method public int transformedToOriginal(int offset);
+ }
+
+ public final class PasswordVisualTransformation implements androidx.ui.input.VisualTransformation {
+ ctor public PasswordVisualTransformation(char mask);
+ ctor public PasswordVisualTransformation();
+ method public androidx.ui.input.TransformedText filter(androidx.ui.text.AnnotatedString text);
+ method public char getMask();
+ }
+
+ public final class TransformedText {
+ ctor public TransformedText(androidx.ui.text.AnnotatedString transformedText, androidx.ui.input.OffsetMap offsetMap);
+ method public androidx.ui.text.AnnotatedString component1();
+ method public androidx.ui.input.OffsetMap component2();
+ method public androidx.ui.input.TransformedText copy(androidx.ui.text.AnnotatedString transformedText, androidx.ui.input.OffsetMap offsetMap);
+ method public androidx.ui.input.OffsetMap getOffsetMap();
+ method public androidx.ui.text.AnnotatedString getTransformedText();
+ }
+
+ public interface VisualTransformation {
+ method public androidx.ui.input.TransformedText filter(androidx.ui.text.AnnotatedString text);
+ }
+
+ public final class VisualTransformationKt {
+ ctor public VisualTransformationKt();
+ }
+
}
package androidx.ui.text {
@@ -77,31 +118,13 @@
method public T! getStyle();
}
- public final class Locale {
- ctor public Locale(String _languageCode, String? _countryCode);
- method public String component1();
- method public String? component2();
- method public androidx.ui.text.Locale copy(String _languageCode, String? _countryCode);
- method public String! getCountryCode();
- method public String getLanguageCode();
- method public String? get_countryCode();
- method public String get_languageCode();
- property public final String! countryCode;
- property public final String languageCode;
- field public static final androidx.ui.text.Locale.Companion! Companion;
- }
-
- public static final class Locale.Companion {
- method public String _canonicalizeLanguageCode(String languageCode);
- method public String _canonicalizeRegionCode(String regionCode);
- }
-
public final class MultiParagraphKt {
ctor public MultiParagraphKt();
}
public interface Paragraph {
method public float getBaseline();
+ method public androidx.ui.text.style.TextDirection getBidiRunDirection(int offset);
method public androidx.ui.engine.geometry.Rect getCursorRect(int offset);
method public boolean getDidExceedMaxLines();
method public float getHeight();
@@ -113,6 +136,7 @@
method public float getMaxIntrinsicWidth();
method public float getMinIntrinsicWidth();
method public int getOffsetForPosition(androidx.ui.core.PxPosition position);
+ method public androidx.ui.text.style.TextDirection getParagraphDirection(int offset);
method public androidx.ui.painting.Path getPathForRange(int start, int end);
method public float getWidth();
method public androidx.ui.text.TextRange getWordBoundary(int offset);
@@ -154,43 +178,9 @@
method public androidx.ui.text.ParagraphStyle merge(androidx.ui.text.ParagraphStyle? other = null);
}
- public final class TextPainter {
- ctor public TextPainter(androidx.ui.text.AnnotatedString? text, androidx.ui.text.TextStyle? style, androidx.ui.text.ParagraphStyle? paragraphStyle, Integer? maxLines, boolean softWrap, androidx.ui.text.style.TextOverflow overflow, androidx.ui.text.Locale? locale, androidx.ui.core.Density density, androidx.ui.text.font.Font.ResourceLoader resourceLoader);
- method public androidx.ui.core.Density getDensity();
- method public boolean getDidExceedMaxLines();
- method public float getHeight();
- method public androidx.ui.text.Locale? getLocale();
- method public float getMaxIntrinsicWidth();
- method public Integer? getMaxLines();
- method public float getMinIntrinsicWidth();
- method public int getOffsetForPosition(androidx.ui.core.PxPosition position);
- method public androidx.ui.text.style.TextOverflow getOverflow();
- method public androidx.ui.text.ParagraphStyle? getParagraphStyle();
- method public float getPreferredLineHeight();
- method public androidx.ui.text.font.Font.ResourceLoader getResourceLoader();
- method public androidx.ui.engine.geometry.Size getSize();
- method public boolean getSoftWrap();
- method public androidx.ui.text.TextStyle? getStyle();
- method public androidx.ui.text.AnnotatedString? getText();
- method public float getWidth();
- method public androidx.ui.text.TextRange getWordBoundary(int offset);
- method public void layout(androidx.ui.core.Constraints constraints);
- method public void paint(androidx.ui.painting.Canvas canvas);
- method public void paintBackground(int start, int end, androidx.ui.graphics.Color color, androidx.ui.painting.Canvas canvas);
- method public void paintCursor(int offset, androidx.ui.painting.Canvas canvas);
- method public void setText(androidx.ui.text.AnnotatedString? value);
- property public final boolean didExceedMaxLines;
- property public final float height;
- property public final float maxIntrinsicWidth;
- property public final float minIntrinsicWidth;
- property public final float preferredLineHeight;
- property public final androidx.ui.engine.geometry.Size size;
- property public final androidx.ui.text.AnnotatedString? text;
- property public final float width;
- }
- public final class TextPainterKt {
- ctor public TextPainterKt();
+ public final class TextDelegateKt {
+ ctor public TextDelegateKt();
}
@RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP) public final class TextSpan {
@@ -209,12 +199,12 @@
}
public final class TextStyle {
- ctor public TextStyle(androidx.ui.graphics.Color? color, androidx.ui.core.Sp? fontSize, Float? fontSizeScale, androidx.ui.text.font.FontWeight? fontWeight, androidx.ui.text.font.FontStyle? fontStyle, androidx.ui.text.font.FontSynthesis? fontSynthesis, androidx.ui.text.font.FontFamily? fontFamily, String? fontFeatureSettings, Float? letterSpacing, androidx.ui.text.style.BaselineShift? baselineShift, androidx.ui.text.style.TextGeometricTransform? textGeometricTransform, androidx.ui.text.Locale? locale, androidx.ui.graphics.Color? background, androidx.ui.text.style.TextDecoration? decoration, androidx.ui.painting.Shadow? shadow);
+ ctor public TextStyle(androidx.ui.graphics.Color? color, androidx.ui.core.Sp? fontSize, Float? fontSizeScale, androidx.ui.text.font.FontWeight? fontWeight, androidx.ui.text.font.FontStyle? fontStyle, androidx.ui.text.font.FontSynthesis? fontSynthesis, androidx.ui.text.font.FontFamily? fontFamily, String? fontFeatureSettings, Float? letterSpacing, androidx.ui.text.style.BaselineShift? baselineShift, androidx.ui.text.style.TextGeometricTransform? textGeometricTransform, java.util.Locale? locale, androidx.ui.graphics.Color? background, androidx.ui.text.style.TextDecoration? decoration, androidx.ui.painting.Shadow? shadow);
ctor public TextStyle();
method public androidx.ui.graphics.Color? component1();
method public androidx.ui.text.style.BaselineShift? component10();
method public androidx.ui.text.style.TextGeometricTransform? component11();
- method public androidx.ui.text.Locale? component12();
+ method public java.util.Locale? component12();
method public androidx.ui.graphics.Color? component13();
method public androidx.ui.text.style.TextDecoration? component14();
method public androidx.ui.painting.Shadow? component15();
@@ -226,7 +216,7 @@
method public androidx.ui.text.font.FontFamily? component7();
method public String? component8();
method public Float? component9();
- method public androidx.ui.text.TextStyle copy(androidx.ui.graphics.Color? color, androidx.ui.core.Sp? fontSize, Float? fontSizeScale, androidx.ui.text.font.FontWeight? fontWeight, androidx.ui.text.font.FontStyle? fontStyle, androidx.ui.text.font.FontSynthesis? fontSynthesis, androidx.ui.text.font.FontFamily? fontFamily, String? fontFeatureSettings, Float? letterSpacing, androidx.ui.text.style.BaselineShift? baselineShift, androidx.ui.text.style.TextGeometricTransform? textGeometricTransform, androidx.ui.text.Locale? locale, androidx.ui.graphics.Color? background, androidx.ui.text.style.TextDecoration? decoration, androidx.ui.painting.Shadow? shadow);
+ method public androidx.ui.text.TextStyle copy(androidx.ui.graphics.Color? color, androidx.ui.core.Sp? fontSize, Float? fontSizeScale, androidx.ui.text.font.FontWeight? fontWeight, androidx.ui.text.font.FontStyle? fontStyle, androidx.ui.text.font.FontSynthesis? fontSynthesis, androidx.ui.text.font.FontFamily? fontFamily, String? fontFeatureSettings, Float? letterSpacing, androidx.ui.text.style.BaselineShift? baselineShift, androidx.ui.text.style.TextGeometricTransform? textGeometricTransform, java.util.Locale? locale, androidx.ui.graphics.Color? background, androidx.ui.text.style.TextDecoration? decoration, androidx.ui.painting.Shadow? shadow);
method public androidx.ui.graphics.Color? getBackground();
method public androidx.ui.text.style.BaselineShift? getBaselineShift();
method public androidx.ui.graphics.Color? getColor();
@@ -239,7 +229,7 @@
method public androidx.ui.text.font.FontSynthesis? getFontSynthesis();
method public androidx.ui.text.font.FontWeight? getFontWeight();
method public Float? getLetterSpacing();
- method public androidx.ui.text.Locale? getLocale();
+ method public java.util.Locale? getLocale();
method public androidx.ui.painting.Shadow? getShadow();
method public androidx.ui.text.style.TextGeometricTransform? getTextGeometricTransform();
method public androidx.ui.text.TextStyle merge(androidx.ui.text.TextStyle? other = null);
diff --git a/ui/ui-text/api/restricted_current.txt b/ui/ui-text/api/restricted_current.txt
index 4850b0f..2610db4 100644
--- a/ui/ui-text/api/restricted_current.txt
+++ b/ui/ui-text/api/restricted_current.txt
@@ -18,6 +18,18 @@
property public final String text;
}
+ public final class EditorStyle {
+ ctor public EditorStyle(androidx.ui.text.TextStyle? textStyle, androidx.ui.graphics.Color compositionColor, androidx.ui.graphics.Color selectionColor);
+ ctor public EditorStyle();
+ method public androidx.ui.text.TextStyle? component1();
+ method public androidx.ui.graphics.Color component2();
+ method public androidx.ui.graphics.Color component3();
+ method public androidx.ui.input.EditorStyle copy(androidx.ui.text.TextStyle? textStyle, androidx.ui.graphics.Color compositionColor, androidx.ui.graphics.Color selectionColor);
+ method public androidx.ui.graphics.Color getCompositionColor();
+ method public androidx.ui.graphics.Color getSelectionColor();
+ method public androidx.ui.text.TextStyle? getTextStyle();
+ }
+
public enum ImeAction {
enum_constant public static final androidx.ui.input.ImeAction Done;
@@ -43,10 +55,39 @@
}
+ public interface OffsetMap {
+ method public int originalToTransformed(int offset);
+ method public int transformedToOriginal(int offset);
+ }
+
+ public final class PasswordVisualTransformation implements androidx.ui.input.VisualTransformation {
+ ctor public PasswordVisualTransformation(char mask);
+ ctor public PasswordVisualTransformation();
+ method public androidx.ui.input.TransformedText filter(androidx.ui.text.AnnotatedString text);
+ method public char getMask();
+ }
+
+ public final class TransformedText {
+ ctor public TransformedText(androidx.ui.text.AnnotatedString transformedText, androidx.ui.input.OffsetMap offsetMap);
+ method public androidx.ui.text.AnnotatedString component1();
+ method public androidx.ui.input.OffsetMap component2();
+ method public androidx.ui.input.TransformedText copy(androidx.ui.text.AnnotatedString transformedText, androidx.ui.input.OffsetMap offsetMap);
+ method public androidx.ui.input.OffsetMap getOffsetMap();
+ method public androidx.ui.text.AnnotatedString getTransformedText();
+ }
+
+ public interface VisualTransformation {
+ method public androidx.ui.input.TransformedText filter(androidx.ui.text.AnnotatedString text);
+ }
+
+ public final class VisualTransformationKt {
+ ctor public VisualTransformationKt();
+ }
+
}
package androidx.ui.text {
@@ -77,31 +118,13 @@
method public T! getStyle();
}
- public final class Locale {
- ctor public Locale(String _languageCode, String? _countryCode);
- method public String component1();
- method public String? component2();
- method public androidx.ui.text.Locale copy(String _languageCode, String? _countryCode);
- method public String! getCountryCode();
- method public String getLanguageCode();
- method public String? get_countryCode();
- method public String get_languageCode();
- property public final String! countryCode;
- property public final String languageCode;
- field public static final androidx.ui.text.Locale.Companion! Companion;
- }
-
- public static final class Locale.Companion {
- method public String _canonicalizeLanguageCode(String languageCode);
- method public String _canonicalizeRegionCode(String regionCode);
- }
-
public final class MultiParagraphKt {
ctor public MultiParagraphKt();
}
public interface Paragraph {
method public float getBaseline();
+ method public androidx.ui.text.style.TextDirection getBidiRunDirection(int offset);
method public androidx.ui.engine.geometry.Rect getCursorRect(int offset);
method public boolean getDidExceedMaxLines();
method public float getHeight();
@@ -113,6 +136,7 @@
method public float getMaxIntrinsicWidth();
method public float getMinIntrinsicWidth();
method public int getOffsetForPosition(androidx.ui.core.PxPosition position);
+ method public androidx.ui.text.style.TextDirection getParagraphDirection(int offset);
method public androidx.ui.painting.Path getPathForRange(int start, int end);
method public float getWidth();
method public androidx.ui.text.TextRange getWordBoundary(int offset);
@@ -154,43 +178,9 @@
method public androidx.ui.text.ParagraphStyle merge(androidx.ui.text.ParagraphStyle? other = null);
}
- public final class TextPainter {
- ctor public TextPainter(androidx.ui.text.AnnotatedString? text, androidx.ui.text.TextStyle? style, androidx.ui.text.ParagraphStyle? paragraphStyle, Integer? maxLines, boolean softWrap, androidx.ui.text.style.TextOverflow overflow, androidx.ui.text.Locale? locale, androidx.ui.core.Density density, androidx.ui.text.font.Font.ResourceLoader resourceLoader);
- method public androidx.ui.core.Density getDensity();
- method public boolean getDidExceedMaxLines();
- method public float getHeight();
- method public androidx.ui.text.Locale? getLocale();
- method public float getMaxIntrinsicWidth();
- method public Integer? getMaxLines();
- method public float getMinIntrinsicWidth();
- method public int getOffsetForPosition(androidx.ui.core.PxPosition position);
- method public androidx.ui.text.style.TextOverflow getOverflow();
- method public androidx.ui.text.ParagraphStyle? getParagraphStyle();
- method public float getPreferredLineHeight();
- method public androidx.ui.text.font.Font.ResourceLoader getResourceLoader();
- method public androidx.ui.engine.geometry.Size getSize();
- method public boolean getSoftWrap();
- method public androidx.ui.text.TextStyle? getStyle();
- method public androidx.ui.text.AnnotatedString? getText();
- method public float getWidth();
- method public androidx.ui.text.TextRange getWordBoundary(int offset);
- method public void layout(androidx.ui.core.Constraints constraints);
- method public void paint(androidx.ui.painting.Canvas canvas);
- method public void paintBackground(int start, int end, androidx.ui.graphics.Color color, androidx.ui.painting.Canvas canvas);
- method public void paintCursor(int offset, androidx.ui.painting.Canvas canvas);
- method public void setText(androidx.ui.text.AnnotatedString? value);
- property public final boolean didExceedMaxLines;
- property public final float height;
- property public final float maxIntrinsicWidth;
- property public final float minIntrinsicWidth;
- property public final float preferredLineHeight;
- property public final androidx.ui.engine.geometry.Size size;
- property public final androidx.ui.text.AnnotatedString? text;
- property public final float width;
- }
- public final class TextPainterKt {
- ctor public TextPainterKt();
+ public final class TextDelegateKt {
+ ctor public TextDelegateKt();
}
@RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP) public final class TextSpan {
@@ -209,12 +199,12 @@
}
public final class TextStyle {
- ctor public TextStyle(androidx.ui.graphics.Color? color, androidx.ui.core.Sp? fontSize, Float? fontSizeScale, androidx.ui.text.font.FontWeight? fontWeight, androidx.ui.text.font.FontStyle? fontStyle, androidx.ui.text.font.FontSynthesis? fontSynthesis, androidx.ui.text.font.FontFamily? fontFamily, String? fontFeatureSettings, Float? letterSpacing, androidx.ui.text.style.BaselineShift? baselineShift, androidx.ui.text.style.TextGeometricTransform? textGeometricTransform, androidx.ui.text.Locale? locale, androidx.ui.graphics.Color? background, androidx.ui.text.style.TextDecoration? decoration, androidx.ui.painting.Shadow? shadow);
+ ctor public TextStyle(androidx.ui.graphics.Color? color, androidx.ui.core.Sp? fontSize, Float? fontSizeScale, androidx.ui.text.font.FontWeight? fontWeight, androidx.ui.text.font.FontStyle? fontStyle, androidx.ui.text.font.FontSynthesis? fontSynthesis, androidx.ui.text.font.FontFamily? fontFamily, String? fontFeatureSettings, Float? letterSpacing, androidx.ui.text.style.BaselineShift? baselineShift, androidx.ui.text.style.TextGeometricTransform? textGeometricTransform, java.util.Locale? locale, androidx.ui.graphics.Color? background, androidx.ui.text.style.TextDecoration? decoration, androidx.ui.painting.Shadow? shadow);
ctor public TextStyle();
method public androidx.ui.graphics.Color? component1();
method public androidx.ui.text.style.BaselineShift? component10();
method public androidx.ui.text.style.TextGeometricTransform? component11();
- method public androidx.ui.text.Locale? component12();
+ method public java.util.Locale? component12();
method public androidx.ui.graphics.Color? component13();
method public androidx.ui.text.style.TextDecoration? component14();
method public androidx.ui.painting.Shadow? component15();
@@ -226,7 +216,7 @@
method public androidx.ui.text.font.FontFamily? component7();
method public String? component8();
method public Float? component9();
- method public androidx.ui.text.TextStyle copy(androidx.ui.graphics.Color? color, androidx.ui.core.Sp? fontSize, Float? fontSizeScale, androidx.ui.text.font.FontWeight? fontWeight, androidx.ui.text.font.FontStyle? fontStyle, androidx.ui.text.font.FontSynthesis? fontSynthesis, androidx.ui.text.font.FontFamily? fontFamily, String? fontFeatureSettings, Float? letterSpacing, androidx.ui.text.style.BaselineShift? baselineShift, androidx.ui.text.style.TextGeometricTransform? textGeometricTransform, androidx.ui.text.Locale? locale, androidx.ui.graphics.Color? background, androidx.ui.text.style.TextDecoration? decoration, androidx.ui.painting.Shadow? shadow);
+ method public androidx.ui.text.TextStyle copy(androidx.ui.graphics.Color? color, androidx.ui.core.Sp? fontSize, Float? fontSizeScale, androidx.ui.text.font.FontWeight? fontWeight, androidx.ui.text.font.FontStyle? fontStyle, androidx.ui.text.font.FontSynthesis? fontSynthesis, androidx.ui.text.font.FontFamily? fontFamily, String? fontFeatureSettings, Float? letterSpacing, androidx.ui.text.style.BaselineShift? baselineShift, androidx.ui.text.style.TextGeometricTransform? textGeometricTransform, java.util.Locale? locale, androidx.ui.graphics.Color? background, androidx.ui.text.style.TextDecoration? decoration, androidx.ui.painting.Shadow? shadow);
method public androidx.ui.graphics.Color? getBackground();
method public androidx.ui.text.style.BaselineShift? getBaselineShift();
method public androidx.ui.graphics.Color? getColor();
@@ -239,7 +229,7 @@
method public androidx.ui.text.font.FontSynthesis? getFontSynthesis();
method public androidx.ui.text.font.FontWeight? getFontWeight();
method public Float? getLetterSpacing();
- method public androidx.ui.text.Locale? getLocale();
+ method public java.util.Locale? getLocale();
method public androidx.ui.painting.Shadow? getShadow();
method public androidx.ui.text.style.TextGeometricTransform? getTextGeometricTransform();
method public androidx.ui.text.TextStyle merge(androidx.ui.text.TextStyle? other = null);
diff --git a/ui/ui-text/integration-tests/text-demos/build.gradle b/ui/ui-text/integration-tests/text-demos/build.gradle
index 389c24b..8254c7e2 100644
--- a/ui/ui-text/integration-tests/text-demos/build.gradle
+++ b/ui/ui-text/integration-tests/text-demos/build.gradle
@@ -20,6 +20,7 @@
implementation project(":compose:compose-runtime")
implementation project(":ui:ui-core")
implementation project(':ui:ui-framework')
+ implementation project(':ui:ui-foundation')
implementation project(':ui:ui-layout')
implementation project(':ui:ui-text')
}
diff --git a/ui/ui-text/integration-tests/text-demos/src/main/java/androidx/ui/text/demos/CraneInputField.kt b/ui/ui-text/integration-tests/text-demos/src/main/java/androidx/ui/text/demos/CraneInputField.kt
index b345b40..f590b27 100644
--- a/ui/ui-text/integration-tests/text-demos/src/main/java/androidx/ui/text/demos/CraneInputField.kt
+++ b/ui/ui-text/integration-tests/text-demos/src/main/java/androidx/ui/text/demos/CraneInputField.kt
@@ -20,14 +20,14 @@
import androidx.compose.Composable
import androidx.compose.state
import androidx.compose.unaryPlus
-import androidx.ui.core.EditorStyle
import androidx.ui.core.TextField
import androidx.ui.input.EditorModel
+import androidx.ui.input.EditorStyle
import androidx.ui.input.ImeAction
import androidx.ui.input.KeyboardType
import androidx.ui.layout.Column
import androidx.ui.layout.CrossAxisAlignment
-import androidx.ui.layout.VerticalScroller
+import androidx.ui.foundation.VerticalScroller
import androidx.ui.text.TextStyle
val KEYBOARD_TYPES = listOf(
diff --git a/ui/ui-text/integration-tests/text-demos/src/main/java/androidx/ui/text/demos/CraneText.kt b/ui/ui-text/integration-tests/text-demos/src/main/java/androidx/ui/text/demos/CraneText.kt
index a4f8e25..a37f2c0 100644
--- a/ui/ui-text/integration-tests/text-demos/src/main/java/androidx/ui/text/demos/CraneText.kt
+++ b/ui/ui-text/integration-tests/text-demos/src/main/java/androidx/ui/text/demos/CraneText.kt
@@ -33,13 +33,12 @@
import androidx.ui.text.style.TextDecoration
import androidx.ui.text.style.TextDirection
import androidx.ui.text.font.FontFamily
-import androidx.ui.text.Locale
import androidx.ui.graphics.Color
import androidx.ui.graphics.lerp
import androidx.ui.layout.Column
import androidx.ui.layout.CrossAxisAlignment
import androidx.ui.layout.Row
-import androidx.ui.layout.VerticalScroller
+import androidx.ui.foundation.VerticalScroller
import androidx.ui.text.ParagraphStyle
import androidx.ui.painting.Shadow
import androidx.compose.composer
@@ -49,6 +48,7 @@
import androidx.ui.core.sp
import androidx.ui.text.AnnotatedString
import androidx.ui.text.style.TextIndent
+import java.util.Locale
val displayText = "Text Demo"
val displayTextChinese = "文本演示"
@@ -377,7 +377,7 @@
text = "$text ",
style = TextStyle(
fontSize = fontSize8,
- locale = Locale(_languageCode = "ja", _countryCode = "JP")
+ locale = Locale("ja", "JP")
)
)
@@ -385,7 +385,7 @@
text = "$text ",
style = TextStyle(
fontSize = fontSize8,
- locale = Locale(_languageCode = "zh", _countryCode = "CN")
+ locale = Locale("zh", "CN")
)
)
@@ -393,7 +393,7 @@
text = text,
style = TextStyle(
fontSize = fontSize8,
- locale = Locale(_languageCode = "zh", _countryCode = "TW")
+ locale = Locale("zh", "TW")
)
)
}
diff --git a/ui/ui-text/integration-tests/text-demos/src/main/java/androidx/ui/text/demos/CraneVariousInputField.kt b/ui/ui-text/integration-tests/text-demos/src/main/java/androidx/ui/text/demos/CraneVariousInputField.kt
index 47b3abb..2614a77 100644
--- a/ui/ui-text/integration-tests/text-demos/src/main/java/androidx/ui/text/demos/CraneVariousInputField.kt
+++ b/ui/ui-text/integration-tests/text-demos/src/main/java/androidx/ui/text/demos/CraneVariousInputField.kt
@@ -20,28 +20,28 @@
import androidx.compose.Composable
import androidx.compose.state
import androidx.compose.unaryPlus
-import androidx.ui.core.EditorStyle
import androidx.ui.core.Layout
-import androidx.ui.core.OffsetMap
-import androidx.ui.core.PasswordVisualTransformation
+import androidx.ui.input.OffsetMap
+import androidx.ui.input.PasswordVisualTransformation
import androidx.ui.core.Text
import androidx.ui.core.TextField
-import androidx.ui.core.TransformedText
-import androidx.ui.core.VisualTransformation
+import androidx.ui.input.TransformedText
+import androidx.ui.input.VisualTransformation
import androidx.ui.core.ipx
import androidx.ui.graphics.Color
import androidx.ui.input.EditorModel
+import androidx.ui.input.EditorStyle
import androidx.ui.input.ImeAction
import androidx.ui.input.KeyboardType
import androidx.ui.layout.Column
import androidx.ui.layout.CrossAxisAlignment
-import androidx.ui.layout.VerticalScroller
+import androidx.ui.foundation.VerticalScroller
import androidx.ui.text.AnnotatedString
import androidx.ui.text.TextStyle
import java.util.Locale
/**
- * The offset translater used for credit card input field.
+ * The offset translator used for credit card input field.
*
* @see creditCardFilter
*/
@@ -272,16 +272,13 @@
if (state.value.text.isNotEmpty()) {
inputField()
} else {
- Layout(
- childrenArray = arrayOf(inputField, hintText),
- layoutBlock = { measurable, constraints ->
- val inputfieldPlacable = measurable[inputField].first().measure(constraints)
- val hintTextPlacable = measurable[hintText].first().measure(constraints)
- layout(inputfieldPlacable.width, inputfieldPlacable.height) {
- inputfieldPlacable.place(0.ipx, 0.ipx)
- hintTextPlacable.place(0.ipx, 0.ipx)
- }
+ Layout(inputField, hintText) { measurable, constraints ->
+ val inputfieldPlacable = measurable[inputField].first().measure(constraints)
+ val hintTextPlacable = measurable[hintText].first().measure(constraints)
+ layout(inputfieldPlacable.width, inputfieldPlacable.height) {
+ inputfieldPlacable.place(0.ipx, 0.ipx)
+ hintTextPlacable.place(0.ipx, 0.ipx)
}
- )
+ }
}
}
\ No newline at end of file
diff --git a/ui/ui-text/src/androidTest/java/androidx/ui/text/MultiParagraphIntegrationTest.kt b/ui/ui-text/src/androidTest/java/androidx/ui/text/MultiParagraphIntegrationTest.kt
index 75c21b6..43292c1 100644
--- a/ui/ui-text/src/androidTest/java/androidx/ui/text/MultiParagraphIntegrationTest.kt
+++ b/ui/ui-text/src/androidTest/java/androidx/ui/text/MultiParagraphIntegrationTest.kt
@@ -40,6 +40,7 @@
import org.junit.Test
import org.junit.runner.RunWith
import org.junit.runners.JUnit4
+import java.util.Locale
@RunWith(JUnit4::class)
@SmallTest
@@ -674,7 +675,7 @@
}
@Test
- fun getPrimaryHorizontal_bidi_singleLine_textDirectionDefault() {
+ fun getPrimaryHorizontal_Bidi_singleLine_textDirectionDefault() {
withDensity(defaultDensity) {
val ltrText = "abc"
val rtlText = "\u05D0\u05D1\u05D2"
@@ -762,7 +763,7 @@
}
@Test
- fun getPrimaryHorizontal_bidi_singleLine_textDirectionLtr() {
+ fun getPrimaryHorizontal_Bidi_singleLine_textDirectionLtr() {
withDensity(defaultDensity) {
val ltrText = "abc"
val rtlText = "\u05D0\u05D1\u05D2"
@@ -800,7 +801,7 @@
}
@Test
- fun getPrimaryHorizontal_bidi_singleLine_textDirectionRtl() {
+ fun getPrimaryHorizontal_Bidi_singleLine_textDirectionRtl() {
withDensity(defaultDensity) {
val ltrText = "abc"
val rtlText = "\u05D0\u05D1\u05D2"
@@ -903,6 +904,584 @@
}
@Test
+ fun getSecondaryHorizontal_ltr_singleLine_textDirectionDefault() {
+ withDensity(defaultDensity) {
+ val text = "abc"
+ val fontSize = 50.sp
+ val fontSizeInPx = fontSize.toPx().value
+ val paragraph = simpleMultiParagraph(text = text, fontSize = fontSize)
+
+ paragraph.layout(ParagraphConstraints(width = text.length * fontSizeInPx))
+
+ for (i in 0..text.length) {
+ assertThat(
+ paragraph.getSecondaryHorizontal(i),
+ equalTo(fontSizeInPx * i)
+ )
+ }
+ }
+ }
+
+ @Test
+ fun getSecondaryHorizontal_rtl_singleLine_textDirectionDefault() {
+ withDensity(defaultDensity) {
+ val text = "\u05D0\u05D1\u05D2"
+ val fontSize = 50.sp
+ val fontSizeInPx = fontSize.toPx().value
+ val paragraph = simpleMultiParagraph(text = text, fontSize = fontSize)
+ val width = text.length * fontSizeInPx
+
+ paragraph.layout(ParagraphConstraints(width))
+
+ for (i in 0..text.length) {
+ assertThat(
+ paragraph.getSecondaryHorizontal(i),
+ equalTo(width - fontSizeInPx * i)
+ )
+ }
+ }
+ }
+
+ @Test
+ fun getSecondaryHorizontal_Bidi_singleLine_textDirectionDefault() {
+ withDensity(defaultDensity) {
+ val ltrText = "abc"
+ val rtlText = "\u05D0\u05D1\u05D2"
+ val text = ltrText + rtlText
+ val fontSize = 50.sp
+ val fontSizeInPx = fontSize.toPx().value
+ val paragraph = simpleMultiParagraph(text = text, fontSize = fontSize)
+ val width = text.length * fontSizeInPx
+
+ paragraph.layout(ParagraphConstraints(width))
+
+ for (i in 0 until ltrText.length) {
+ assertThat(
+ paragraph.getSecondaryHorizontal(i),
+ equalTo(fontSizeInPx * i)
+ )
+ }
+
+ for (i in 0..rtlText.length) {
+ assertThat(
+ paragraph.getSecondaryHorizontal(i + ltrText.length),
+ equalTo(width - fontSizeInPx * i)
+ )
+ }
+ }
+ }
+
+ @Test
+ fun getSecondaryHorizontal_ltr_singleLine_textDirectionRtl() {
+ withDensity(defaultDensity) {
+ val text = "abc"
+ val fontSize = 50.sp
+ val fontSizeInPx = fontSize.toPx().value
+ val paragraph = simpleMultiParagraph(
+ text = text,
+ fontSize = fontSize,
+ textDirection = TextDirection.Rtl
+ )
+ val width = text.length * fontSizeInPx
+ paragraph.layout(ParagraphConstraints(width))
+
+ assertThat(paragraph.getSecondaryHorizontal(0), equalTo(0f))
+
+ for (i in 1 until text.length) {
+ assertThat(
+ paragraph.getSecondaryHorizontal(i),
+ equalTo(fontSizeInPx * i)
+ )
+ }
+
+ assertThat(paragraph.getSecondaryHorizontal(text.length), equalTo(width))
+ }
+ }
+
+ @Test
+ fun getSecondaryHorizontal_rtl_singleLine_textDirectionLtr() {
+ withDensity(defaultDensity) {
+ val text = "\u05D0\u05D1\u05D2"
+ val fontSize = 50.sp
+ val fontSizeInPx = fontSize.toPx().value
+ val paragraph = simpleMultiParagraph(
+ text = text,
+ fontSize = fontSize,
+ textDirection = TextDirection.Ltr
+ )
+ val width = text.length * fontSizeInPx
+ paragraph.layout(ParagraphConstraints(width))
+
+ assertThat(paragraph.getSecondaryHorizontal(0), equalTo(width))
+
+ for (i in 1 until text.length) {
+ assertThat(
+ paragraph.getSecondaryHorizontal(i),
+ equalTo(width - fontSizeInPx * i)
+ )
+ }
+
+ assertThat(paragraph.getSecondaryHorizontal(text.length), equalTo(0f))
+ }
+ }
+
+ @Test
+ fun getSecondaryHorizontal_Bidi_singleLine_textDirectionLtr() {
+ withDensity(defaultDensity) {
+ val ltrText = "abc"
+ val rtlText = "\u05D0\u05D1\u05D2"
+ val text = ltrText + rtlText
+ val fontSize = 50.sp
+ val fontSizeInPx = fontSize.toPx().value
+ val paragraph = simpleMultiParagraph(
+ text = text,
+ fontSize = fontSize,
+ textDirection = TextDirection.Ltr
+ )
+ val width = text.length * fontSizeInPx
+
+ paragraph.layout(ParagraphConstraints(width))
+
+ for (i in 0 until ltrText.length) {
+ assertThat(
+ paragraph.getSecondaryHorizontal(i),
+ equalTo(fontSizeInPx * i)
+ )
+ }
+
+ for (i in 0 until rtlText.length) {
+ assertThat(
+ paragraph.getSecondaryHorizontal(i + ltrText.length),
+ equalTo(width - fontSizeInPx * i)
+ )
+ }
+
+ assertThat(
+ paragraph.getSecondaryHorizontal(text.length),
+ equalTo(width - rtlText.length * fontSizeInPx)
+ )
+ }
+ }
+
+ @Test
+ fun getSecondaryHorizontal_Bidi_singleLine_textDirectionRtl() {
+ withDensity(defaultDensity) {
+ val ltrText = "abc"
+ val rtlText = "\u05D0\u05D1\u05D2"
+ val text = ltrText + rtlText
+ val fontSize = 50.sp
+ val fontSizeInPx = fontSize.toPx().value
+ val paragraph = simpleMultiParagraph(
+ text = text,
+ fontSize = fontSize,
+ textDirection = TextDirection.Rtl
+ )
+ val width = text.length * fontSizeInPx
+
+ paragraph.layout(ParagraphConstraints(width))
+
+ assertThat(
+ paragraph.getSecondaryHorizontal(0),
+ equalTo(width - ltrText.length * fontSizeInPx)
+ )
+ for (i in 1..ltrText.length) {
+ assertThat(
+ paragraph.getSecondaryHorizontal(i),
+ equalTo(rtlText.length * fontSizeInPx + i * fontSizeInPx)
+ )
+ }
+
+ for (i in 1..rtlText.length) {
+ assertThat(
+ paragraph.getSecondaryHorizontal(i + ltrText.length),
+ equalTo(rtlText.length * fontSizeInPx - i * fontSizeInPx)
+ )
+ }
+ }
+ }
+
+ @Test
+ fun getSecondaryHorizontal_ltr_newLine_textDirectionDefault() {
+ withDensity(defaultDensity) {
+ val text = "abc\n"
+ val fontSize = 50.sp
+ val fontSizeInPx = fontSize.toPx().value
+ val paragraph = simpleMultiParagraph(text = text, fontSize = fontSize)
+ val width = text.length * fontSizeInPx
+
+ paragraph.layout(ParagraphConstraints(width))
+
+ assertThat(paragraph.getSecondaryHorizontal(text.length), equalTo(0f))
+ }
+ }
+
+ @Test
+ fun getSecondaryHorizontal_rtl_newLine_textDirectionDefault() {
+ withDensity(defaultDensity) {
+ val text = "\u05D0\u05D1\u05D2\n"
+ val fontSize = 50.sp
+ val fontSizeInPx = fontSize.toPx().value
+ val paragraph = simpleMultiParagraph(text = text, fontSize = fontSize)
+ val width = text.length * fontSizeInPx
+
+ paragraph.layout(ParagraphConstraints(width))
+
+ assertThat(paragraph.getSecondaryHorizontal(text.length), equalTo(0f))
+ }
+ }
+
+ @Test
+ fun getSecondaryHorizontal_ltr_newLine_textDirectionRtl() {
+ withDensity(defaultDensity) {
+ val text = "abc\n"
+ val fontSize = 50.sp
+ val fontSizeInPx = fontSize.toPx().value
+ val paragraph = simpleMultiParagraph(
+ text = text,
+ fontSize = fontSize,
+ textDirection = TextDirection.Rtl
+ )
+ val width = text.length * fontSizeInPx
+
+ paragraph.layout(ParagraphConstraints(width))
+
+ assertThat(paragraph.getSecondaryHorizontal(text.length), equalTo(width))
+ }
+ }
+
+ @Test
+ fun getSecondaryHorizontal_rtl_newLine_textDirectionLtr() {
+ withDensity(defaultDensity) {
+ val text = "\u05D0\u05D1\u05D2\n"
+ val fontSize = 50.sp
+ val fontSizeInPx = fontSize.toPx().value
+ val paragraph = simpleMultiParagraph(
+ text = text,
+ fontSize = fontSize,
+ textDirection = TextDirection.Ltr
+ )
+ val width = text.length * fontSizeInPx
+
+ paragraph.layout(ParagraphConstraints(width))
+
+ assertThat(paragraph.getSecondaryHorizontal(text.length), equalTo(0f))
+ }
+ }
+
+ @Test
+ fun getParagraphDirection_ltr_singleLine_textDirectionDefault() {
+ withDensity(defaultDensity) {
+ val text = "abc"
+ val fontSize = 50.sp
+ val fontSizeInPx = fontSize.toPx().value
+ val paragraph = simpleMultiParagraph(
+ text = text,
+ fontSize = fontSize
+ )
+ val width = text.length * fontSizeInPx
+
+ paragraph.layout(ParagraphConstraints(width))
+
+ for (i in 0..text.length) {
+ assertThat(paragraph.getParagraphDirection(i), equalTo(TextDirection.Ltr))
+ }
+ }
+ }
+
+ @Test
+ fun getParagraphDirection_ltr_singleLine_textDirectionRtl() {
+ withDensity(defaultDensity) {
+ val text = "abc"
+ val fontSize = 50.sp
+ val fontSizeInPx = fontSize.toPx().value
+ val paragraph = simpleMultiParagraph(
+ text = text,
+ fontSize = fontSize,
+ textDirection = TextDirection.Rtl
+ )
+ val width = text.length * fontSizeInPx
+
+ paragraph.layout(ParagraphConstraints(width))
+
+ for (i in 0..text.length) {
+ assertThat(paragraph.getParagraphDirection(i), equalTo(TextDirection.Rtl))
+ }
+ }
+ }
+
+ @Test
+ fun getParagraphDirection_rtl_singleLine_textDirectionDefault() {
+ withDensity(defaultDensity) {
+ val text = "\u05D0\u05D1\u05D2\n"
+ val fontSize = 50.sp
+ val fontSizeInPx = fontSize.toPx().value
+ val paragraph = simpleMultiParagraph(
+ text = text,
+ fontSize = fontSize
+ )
+ val width = text.length * fontSizeInPx
+
+ paragraph.layout(ParagraphConstraints(width))
+
+ for (i in 0 until text.length) {
+ assertThat(paragraph.getParagraphDirection(i), equalTo(TextDirection.Rtl))
+ }
+ }
+ }
+
+ @Test
+ fun getParagraphDirection_rtl_singleLine_textDirectionLtr() {
+ withDensity(defaultDensity) {
+ val text = "abc"
+ val fontSize = 50.sp
+ val fontSizeInPx = fontSize.toPx().value
+ val paragraph = simpleMultiParagraph(
+ text = text,
+ fontSize = fontSize,
+ textDirection = TextDirection.Ltr
+ )
+ val width = text.length * fontSizeInPx
+
+ paragraph.layout(ParagraphConstraints(width))
+
+ for (i in 0..text.length) {
+ assertThat(paragraph.getParagraphDirection(i), equalTo(TextDirection.Ltr))
+ }
+ }
+ }
+
+ @Test
+ fun getParagraphDirection_Bidi_singleLine_textDirectionDefault() {
+ withDensity(defaultDensity) {
+ val ltrText = "abc"
+ val rtlText = "\u05D0\u05D1\u05D2"
+ val text = ltrText + rtlText
+ val fontSize = 50.sp
+ val fontSizeInPx = fontSize.toPx().value
+ val paragraph = simpleMultiParagraph(
+ text = text,
+ fontSize = fontSize
+ )
+ val width = text.length * fontSizeInPx
+
+ paragraph.layout(ParagraphConstraints(width))
+
+ for (i in 0..text.length) {
+ assertThat(paragraph.getParagraphDirection(i), equalTo(TextDirection.Ltr))
+ }
+ }
+ }
+
+ @Test
+ fun getParagraphDirection_Bidi_singleLine_textDirectionLtr() {
+ withDensity(defaultDensity) {
+ val ltrText = "abc"
+ val rtlText = "\u05D0\u05D1\u05D2"
+ val text = ltrText + rtlText
+ val fontSize = 50.sp
+ val fontSizeInPx = fontSize.toPx().value
+ val paragraph = simpleMultiParagraph(
+ text = text,
+ fontSize = fontSize,
+ textDirection = TextDirection.Ltr
+ )
+ val width = text.length * fontSizeInPx
+
+ paragraph.layout(ParagraphConstraints(width))
+
+ for (i in 0..text.length) {
+ assertThat(paragraph.getParagraphDirection(i), equalTo(TextDirection.Ltr))
+ }
+ }
+ }
+
+ @Test
+ fun getParagraphDirection_Bidi_singleLine_textDirectionRtl() {
+ withDensity(defaultDensity) {
+ val ltrText = "abc"
+ val rtlText = "\u05D0\u05D1\u05D2"
+ val text = ltrText + rtlText
+ val fontSize = 50.sp
+ val fontSizeInPx = fontSize.toPx().value
+ val paragraph = simpleMultiParagraph(
+ text = text,
+ fontSize = fontSize,
+ textDirection = TextDirection.Rtl
+ )
+ val width = text.length * fontSizeInPx
+
+ paragraph.layout(ParagraphConstraints(width))
+
+ for (i in 0..text.length) {
+ assertThat(paragraph.getParagraphDirection(i), equalTo(TextDirection.Rtl))
+ }
+ }
+ }
+
+ @Test
+ fun getBidiRunDirection_ltr_singleLine_textDirectionDefault() {
+ withDensity(defaultDensity) {
+ val text = "abc"
+ val fontSize = 50.sp
+ val fontSizeInPx = fontSize.toPx().value
+ val paragraph = simpleMultiParagraph(
+ text = text,
+ fontSize = fontSize
+ )
+ val width = text.length * fontSizeInPx
+
+ paragraph.layout(ParagraphConstraints(width))
+
+ for (i in 0..text.length) {
+ assertThat(paragraph.getBidiRunDirection(i), equalTo(TextDirection.Ltr))
+ }
+ }
+ }
+
+ @Test
+ fun getBidiRunDirection_ltr_singleLine_textDirectionRtl() {
+ withDensity(defaultDensity) {
+ val text = "abc"
+ val fontSize = 50.sp
+ val fontSizeInPx = fontSize.toPx().value
+ val paragraph = simpleMultiParagraph(
+ text = text,
+ fontSize = fontSize,
+ textDirection = TextDirection.Rtl
+ )
+ val width = text.length * fontSizeInPx
+
+ paragraph.layout(ParagraphConstraints(width))
+
+ for (i in 0..text.length) {
+ assertThat(paragraph.getBidiRunDirection(i), equalTo(TextDirection.Ltr))
+ }
+ }
+ }
+
+ @Test
+ fun getBidiRunDirection_rtl_singleLine_textDirectionDefault() {
+ withDensity(defaultDensity) {
+ val text = "\u05D0\u05D1\u05D2\n"
+ val fontSize = 50.sp
+ val fontSizeInPx = fontSize.toPx().value
+ val paragraph = simpleMultiParagraph(
+ text = text,
+ fontSize = fontSize
+ )
+ val width = text.length * fontSizeInPx
+
+ paragraph.layout(ParagraphConstraints(width))
+
+ for (i in 0 until text.length) {
+ assertThat(paragraph.getBidiRunDirection(i), equalTo(TextDirection.Rtl))
+ }
+ }
+ }
+
+ @Test
+ fun getBidiRunDirection_rtl_singleLine_textDirectionLtr() {
+ withDensity(defaultDensity) {
+ val text = "\u05D0\u05D1\u05D2\n"
+ val fontSize = 50.sp
+ val fontSizeInPx = fontSize.toPx().value
+ val paragraph = simpleMultiParagraph(
+ text = text,
+ fontSize = fontSize,
+ textDirection = TextDirection.Ltr
+ )
+ val width = text.length * fontSizeInPx
+
+ paragraph.layout(ParagraphConstraints(width))
+
+ for (i in 0 until text.length - 1) {
+ assertThat(paragraph.getBidiRunDirection(i), equalTo(TextDirection.Rtl))
+ }
+ assertThat(paragraph.getBidiRunDirection(text.length - 1), equalTo(TextDirection.Ltr))
+ }
+ }
+
+ @Test
+ fun getBidiRunDirection_Bidi_singleLine_textDirectionDefault() {
+ withDensity(defaultDensity) {
+ val ltrText = "abc"
+ val rtlText = "\u05D0\u05D1\u05D2"
+ val text = ltrText + rtlText
+ val fontSize = 50.sp
+ val fontSizeInPx = fontSize.toPx().value
+ val paragraph = simpleMultiParagraph(
+ text = text,
+ fontSize = fontSize
+ )
+ val width = text.length * fontSizeInPx
+
+ paragraph.layout(ParagraphConstraints(width))
+
+ for (i in 0 until ltrText.length) {
+ assertThat(paragraph.getBidiRunDirection(i), equalTo(TextDirection.Ltr))
+ }
+
+ for (i in ltrText.length until text.length) {
+ assertThat(paragraph.getBidiRunDirection(i), equalTo(TextDirection.Rtl))
+ }
+ }
+ }
+
+ @Test
+ fun getBidiRunDirection_Bidi_singleLine_textDirectionLtr() {
+ withDensity(defaultDensity) {
+ val ltrText = "abc"
+ val rtlText = "\u05D0\u05D1\u05D2"
+ val text = ltrText + rtlText
+ val fontSize = 50.sp
+ val fontSizeInPx = fontSize.toPx().value
+ val paragraph = simpleMultiParagraph(
+ text = text,
+ fontSize = fontSize,
+ textDirection = TextDirection.Ltr
+ )
+ val width = text.length * fontSizeInPx
+
+ paragraph.layout(ParagraphConstraints(width))
+
+ for (i in 0 until ltrText.length) {
+ assertThat(paragraph.getBidiRunDirection(i), equalTo(TextDirection.Ltr))
+ }
+
+ for (i in ltrText.length until text.length) {
+ assertThat(paragraph.getBidiRunDirection(i), equalTo(TextDirection.Rtl))
+ }
+ }
+ }
+
+ @Test
+ fun getBidiRunDirection_Bidi_singleLine_textDirectionRtl() {
+ withDensity(defaultDensity) {
+ val ltrText = "abc"
+ val rtlText = "\u05D0\u05D1\u05D2"
+ val text = ltrText + rtlText
+ val fontSize = 50.sp
+ val fontSizeInPx = fontSize.toPx().value
+ val paragraph = simpleMultiParagraph(
+ text = text,
+ fontSize = fontSize,
+ textDirection = TextDirection.Rtl
+ )
+ val width = text.length * fontSizeInPx
+
+ paragraph.layout(ParagraphConstraints(width))
+
+ for (i in 0 until ltrText.length) {
+ assertThat(paragraph.getBidiRunDirection(i), equalTo(TextDirection.Ltr))
+ }
+
+ for (i in ltrText.length until text.length) {
+ assertThat(paragraph.getBidiRunDirection(i), equalTo(TextDirection.Rtl))
+ }
+ }
+ }
+
+ @Test
fun getLineForOffset_singleLine() {
withDensity(defaultDensity) {
val text = "abc"
diff --git a/ui/ui-text/src/androidTest/java/androidx/ui/text/ParagraphIntegrationTest.kt b/ui/ui-text/src/androidTest/java/androidx/ui/text/ParagraphIntegrationTest.kt
index 9718437..09dbfc6 100644
--- a/ui/ui-text/src/androidTest/java/androidx/ui/text/ParagraphIntegrationTest.kt
+++ b/ui/ui-text/src/androidTest/java/androidx/ui/text/ParagraphIntegrationTest.kt
@@ -35,19 +35,25 @@
import androidx.ui.text.font.FontFamily
import androidx.ui.text.font.asFontFamily
import androidx.ui.graphics.Color
+import androidx.ui.painting.Canvas
+import androidx.ui.painting.Image
+import androidx.ui.painting.ImageConfig
import androidx.ui.text.matchers.equalToBitmap
import androidx.ui.painting.Path
import androidx.ui.painting.PathOperation
import androidx.ui.painting.Shadow
import androidx.ui.text.style.TextAlign
import androidx.ui.text.style.TextIndent
-import com.nhaarman.mockitokotlin2.mock
import org.hamcrest.Matchers.equalTo
+import org.hamcrest.Matchers.greaterThan
import org.hamcrest.Matchers.not
import org.junit.Assert.assertThat
import org.junit.Test
import org.junit.runner.RunWith
import org.junit.runners.JUnit4
+import java.util.Locale
+import org.mockito.Mockito.mock
+import kotlin.math.roundToInt
@RunWith(JUnit4::class)
@SmallTest
@@ -765,7 +771,7 @@
}
@Test
- fun getPrimaryHorizontal_bidi_singleLine_textDirectionDefault() {
+ fun getPrimaryHorizontal_Bidi_singleLine_textDirectionDefault() {
withDensity(defaultDensity) {
val ltrText = "abc"
val rtlText = "\u05D0\u05D1\u05D2"
@@ -853,7 +859,7 @@
}
@Test
- fun getPrimaryHorizontal_bidi_singleLine_textDirectionLtr() {
+ fun getPrimaryHorizontal_Bidi_singleLine_textDirectionLtr() {
withDensity(defaultDensity) {
val ltrText = "abc"
val rtlText = "\u05D0\u05D1\u05D2"
@@ -891,7 +897,7 @@
}
@Test
- fun getPrimaryHorizontal_bidi_singleLine_textDirectionRtl() {
+ fun getPrimaryHorizontal_Bidi_singleLine_textDirectionRtl() {
withDensity(defaultDensity) {
val ltrText = "abc"
val rtlText = "\u05D0\u05D1\u05D2"
@@ -993,6 +999,584 @@
}
@Test
+ fun getSecondaryHorizontal_ltr_singleLine_textDirectionDefault() {
+ withDensity(defaultDensity) {
+ val text = "abc"
+ val fontSize = 50.sp
+ val fontSizeInPx = fontSize.toPx().value
+ val paragraph = simpleParagraph(text = text, fontSize = fontSize)
+
+ paragraph.layout(ParagraphConstraints(width = text.length * fontSizeInPx))
+
+ for (i in 0..text.length) {
+ assertThat(
+ paragraph.getSecondaryHorizontal(i),
+ equalTo(fontSizeInPx * i)
+ )
+ }
+ }
+ }
+
+ @Test
+ fun getSecondaryHorizontal_rtl_singleLine_textDirectionDefault() {
+ withDensity(defaultDensity) {
+ val text = "\u05D0\u05D1\u05D2"
+ val fontSize = 50.sp
+ val fontSizeInPx = fontSize.toPx().value
+ val paragraph = simpleParagraph(text = text, fontSize = fontSize)
+ val width = text.length * fontSizeInPx
+
+ paragraph.layout(ParagraphConstraints(width))
+
+ for (i in 0..text.length) {
+ assertThat(
+ paragraph.getSecondaryHorizontal(i),
+ equalTo(width - fontSizeInPx * i)
+ )
+ }
+ }
+ }
+
+ @Test
+ fun getSecondaryHorizontal_Bidi_singleLine_textDirectionDefault() {
+ withDensity(defaultDensity) {
+ val ltrText = "abc"
+ val rtlText = "\u05D0\u05D1\u05D2"
+ val text = ltrText + rtlText
+ val fontSize = 50.sp
+ val fontSizeInPx = fontSize.toPx().value
+ val paragraph = simpleParagraph(text = text, fontSize = fontSize)
+ val width = text.length * fontSizeInPx
+
+ paragraph.layout(ParagraphConstraints(width))
+
+ for (i in 0 until ltrText.length) {
+ assertThat(
+ paragraph.getSecondaryHorizontal(i),
+ equalTo(fontSizeInPx * i)
+ )
+ }
+
+ for (i in 0..rtlText.length) {
+ assertThat(
+ paragraph.getSecondaryHorizontal(i + ltrText.length),
+ equalTo(width - fontSizeInPx * i)
+ )
+ }
+ }
+ }
+
+ @Test
+ fun getSecondaryHorizontal_ltr_singleLine_textDirectionRtl() {
+ withDensity(defaultDensity) {
+ val text = "abc"
+ val fontSize = 50.sp
+ val fontSizeInPx = fontSize.toPx().value
+ val paragraph = simpleParagraph(
+ text = text,
+ fontSize = fontSize,
+ textDirection = TextDirection.Rtl
+ )
+ val width = text.length * fontSizeInPx
+ paragraph.layout(ParagraphConstraints(width))
+
+ assertThat(paragraph.getSecondaryHorizontal(0), equalTo(0f))
+
+ for (i in 1 until text.length) {
+ assertThat(
+ paragraph.getSecondaryHorizontal(i),
+ equalTo(fontSizeInPx * i)
+ )
+ }
+
+ assertThat(paragraph.getSecondaryHorizontal(text.length), equalTo(width))
+ }
+ }
+
+ @Test
+ fun getSecondaryHorizontal_rtl_singleLine_textDirectionLtr() {
+ withDensity(defaultDensity) {
+ val text = "\u05D0\u05D1\u05D2"
+ val fontSize = 50.sp
+ val fontSizeInPx = fontSize.toPx().value
+ val paragraph = simpleParagraph(
+ text = text,
+ fontSize = fontSize,
+ textDirection = TextDirection.Ltr
+ )
+ val width = text.length * fontSizeInPx
+ paragraph.layout(ParagraphConstraints(width))
+
+ assertThat(paragraph.getSecondaryHorizontal(0), equalTo(width))
+
+ for (i in 1 until text.length) {
+ assertThat(
+ paragraph.getSecondaryHorizontal(i),
+ equalTo(width - fontSizeInPx * i)
+ )
+ }
+
+ assertThat(paragraph.getSecondaryHorizontal(text.length), equalTo(0f))
+ }
+ }
+
+ @Test
+ fun getSecondaryHorizontal_Bidi_singleLine_textDirectionLtr() {
+ withDensity(defaultDensity) {
+ val ltrText = "abc"
+ val rtlText = "\u05D0\u05D1\u05D2"
+ val text = ltrText + rtlText
+ val fontSize = 50.sp
+ val fontSizeInPx = fontSize.toPx().value
+ val paragraph = simpleParagraph(
+ text = text,
+ fontSize = fontSize,
+ textDirection = TextDirection.Ltr
+ )
+ val width = text.length * fontSizeInPx
+
+ paragraph.layout(ParagraphConstraints(width))
+
+ for (i in 0 until ltrText.length) {
+ assertThat(
+ paragraph.getSecondaryHorizontal(i),
+ equalTo(fontSizeInPx * i)
+ )
+ }
+
+ for (i in 0 until rtlText.length) {
+ assertThat(
+ paragraph.getSecondaryHorizontal(i + ltrText.length),
+ equalTo(width - fontSizeInPx * i)
+ )
+ }
+
+ assertThat(
+ paragraph.getSecondaryHorizontal(text.length),
+ equalTo(width - rtlText.length * fontSizeInPx)
+ )
+ }
+ }
+
+ @Test
+ fun getSecondaryHorizontal_Bidi_singleLine_textDirectionRtl() {
+ withDensity(defaultDensity) {
+ val ltrText = "abc"
+ val rtlText = "\u05D0\u05D1\u05D2"
+ val text = ltrText + rtlText
+ val fontSize = 50.sp
+ val fontSizeInPx = fontSize.toPx().value
+ val paragraph = simpleParagraph(
+ text = text,
+ fontSize = fontSize,
+ textDirection = TextDirection.Rtl
+ )
+ val width = text.length * fontSizeInPx
+
+ paragraph.layout(ParagraphConstraints(width))
+
+ assertThat(
+ paragraph.getSecondaryHorizontal(0),
+ equalTo(width - ltrText.length * fontSizeInPx)
+ )
+ for (i in 1..ltrText.length) {
+ assertThat(
+ paragraph.getSecondaryHorizontal(i),
+ equalTo(rtlText.length * fontSizeInPx + i * fontSizeInPx)
+ )
+ }
+
+ for (i in 1..rtlText.length) {
+ assertThat(
+ paragraph.getSecondaryHorizontal(i + ltrText.length),
+ equalTo(rtlText.length * fontSizeInPx - i * fontSizeInPx)
+ )
+ }
+ }
+ }
+
+ @Test
+ fun getSecondaryHorizontal_ltr_newLine_textDirectionDefault() {
+ withDensity(defaultDensity) {
+ val text = "abc\n"
+ val fontSize = 50.sp
+ val fontSizeInPx = fontSize.toPx().value
+ val paragraph = simpleParagraph(text = text, fontSize = fontSize)
+ val width = text.length * fontSizeInPx
+
+ paragraph.layout(ParagraphConstraints(width))
+
+ assertThat(paragraph.getSecondaryHorizontal(text.length), equalTo(0f))
+ }
+ }
+
+ @Test
+ fun getSecondaryHorizontal_rtl_newLine_textDirectionDefault() {
+ withDensity(defaultDensity) {
+ val text = "\u05D0\u05D1\u05D2\n"
+ val fontSize = 50.sp
+ val fontSizeInPx = fontSize.toPx().value
+ val paragraph = simpleParagraph(text = text, fontSize = fontSize)
+ val width = text.length * fontSizeInPx
+
+ paragraph.layout(ParagraphConstraints(width))
+
+ assertThat(paragraph.getSecondaryHorizontal(text.length), equalTo(0f))
+ }
+ }
+
+ @Test
+ fun getSecondaryHorizontal_ltr_newLine_textDirectionRtl() {
+ withDensity(defaultDensity) {
+ val text = "abc\n"
+ val fontSize = 50.sp
+ val fontSizeInPx = fontSize.toPx().value
+ val paragraph = simpleParagraph(
+ text = text,
+ fontSize = fontSize,
+ textDirection = TextDirection.Rtl
+ )
+ val width = text.length * fontSizeInPx
+
+ paragraph.layout(ParagraphConstraints(width))
+
+ assertThat(paragraph.getSecondaryHorizontal(text.length), equalTo(width))
+ }
+ }
+
+ @Test
+ fun getSecondaryHorizontal_rtl_newLine_textDirectionLtr() {
+ withDensity(defaultDensity) {
+ val text = "\u05D0\u05D1\u05D2\n"
+ val fontSize = 50.sp
+ val fontSizeInPx = fontSize.toPx().value
+ val paragraph = simpleParagraph(
+ text = text,
+ fontSize = fontSize,
+ textDirection = TextDirection.Ltr
+ )
+ val width = text.length * fontSizeInPx
+
+ paragraph.layout(ParagraphConstraints(width))
+
+ assertThat(paragraph.getSecondaryHorizontal(text.length), equalTo(0f))
+ }
+ }
+
+ @Test
+ fun getParagraphDirection_ltr_singleLine_textDirectionDefault() {
+ withDensity(defaultDensity) {
+ val text = "abc"
+ val fontSize = 50.sp
+ val fontSizeInPx = fontSize.toPx().value
+ val paragraph = simpleParagraph(
+ text = text,
+ fontSize = fontSize
+ )
+ val width = text.length * fontSizeInPx
+
+ paragraph.layout(ParagraphConstraints(width))
+
+ for (i in 0..text.length) {
+ assertThat(paragraph.getParagraphDirection(i), equalTo(TextDirection.Ltr))
+ }
+ }
+ }
+
+ @Test
+ fun getParagraphDirection_ltr_singleLine_textDirectionRtl() {
+ withDensity(defaultDensity) {
+ val text = "abc"
+ val fontSize = 50.sp
+ val fontSizeInPx = fontSize.toPx().value
+ val paragraph = simpleParagraph(
+ text = text,
+ fontSize = fontSize,
+ textDirection = TextDirection.Rtl
+ )
+ val width = text.length * fontSizeInPx
+
+ paragraph.layout(ParagraphConstraints(width))
+
+ for (i in 0..text.length) {
+ assertThat(paragraph.getParagraphDirection(i), equalTo(TextDirection.Rtl))
+ }
+ }
+ }
+
+ @Test
+ fun getParagraphDirection_rtl_singleLine_textDirectionDefault() {
+ withDensity(defaultDensity) {
+ val text = "\u05D0\u05D1\u05D2\n"
+ val fontSize = 50.sp
+ val fontSizeInPx = fontSize.toPx().value
+ val paragraph = simpleParagraph(
+ text = text,
+ fontSize = fontSize
+ )
+ val width = text.length * fontSizeInPx
+
+ paragraph.layout(ParagraphConstraints(width))
+
+ for (i in 0 until text.length) {
+ assertThat(paragraph.getParagraphDirection(i), equalTo(TextDirection.Rtl))
+ }
+ }
+ }
+
+ @Test
+ fun getParagraphDirection_rtl_singleLine_textDirectionLtr() {
+ withDensity(defaultDensity) {
+ val text = "\u05D0\u05D1\u05D2\n"
+ val fontSize = 50.sp
+ val fontSizeInPx = fontSize.toPx().value
+ val paragraph = simpleParagraph(
+ text = text,
+ fontSize = fontSize,
+ textDirection = TextDirection.Ltr
+ )
+ val width = text.length * fontSizeInPx
+
+ paragraph.layout(ParagraphConstraints(width))
+
+ for (i in 0..text.length) {
+ assertThat(paragraph.getParagraphDirection(i), equalTo(TextDirection.Ltr))
+ }
+ }
+ }
+
+ @Test
+ fun getParagraphDirection_Bidi_singleLine_textDirectionDefault() {
+ withDensity(defaultDensity) {
+ val ltrText = "abc"
+ val rtlText = "\u05D0\u05D1\u05D2"
+ val text = ltrText + rtlText
+ val fontSize = 50.sp
+ val fontSizeInPx = fontSize.toPx().value
+ val paragraph = simpleParagraph(
+ text = text,
+ fontSize = fontSize
+ )
+ val width = text.length * fontSizeInPx
+
+ paragraph.layout(ParagraphConstraints(width))
+
+ for (i in 0..text.length) {
+ assertThat(paragraph.getParagraphDirection(i), equalTo(TextDirection.Ltr))
+ }
+ }
+ }
+
+ @Test
+ fun getParagraphDirection_Bidi_singleLine_textDirectionLtr() {
+ withDensity(defaultDensity) {
+ val ltrText = "abc"
+ val rtlText = "\u05D0\u05D1\u05D2"
+ val text = ltrText + rtlText
+ val fontSize = 50.sp
+ val fontSizeInPx = fontSize.toPx().value
+ val paragraph = simpleParagraph(
+ text = text,
+ fontSize = fontSize,
+ textDirection = TextDirection.Ltr
+ )
+ val width = text.length * fontSizeInPx
+
+ paragraph.layout(ParagraphConstraints(width))
+
+ for (i in 0..text.length) {
+ assertThat(paragraph.getParagraphDirection(i), equalTo(TextDirection.Ltr))
+ }
+ }
+ }
+
+ @Test
+ fun getParagraphDirection_Bidi_singleLine_textDirectionRtl() {
+ withDensity(defaultDensity) {
+ val ltrText = "abc"
+ val rtlText = "\u05D0\u05D1\u05D2"
+ val text = ltrText + rtlText
+ val fontSize = 50.sp
+ val fontSizeInPx = fontSize.toPx().value
+ val paragraph = simpleParagraph(
+ text = text,
+ fontSize = fontSize,
+ textDirection = TextDirection.Rtl
+ )
+ val width = text.length * fontSizeInPx
+
+ paragraph.layout(ParagraphConstraints(width))
+
+ for (i in 0..text.length) {
+ assertThat(paragraph.getParagraphDirection(i), equalTo(TextDirection.Rtl))
+ }
+ }
+ }
+
+ @Test
+ fun getBidiRunDirection_ltr_singleLine_textDirectionDefault() {
+ withDensity(defaultDensity) {
+ val text = "abc"
+ val fontSize = 50.sp
+ val fontSizeInPx = fontSize.toPx().value
+ val paragraph = simpleParagraph(
+ text = text,
+ fontSize = fontSize
+ )
+ val width = text.length * fontSizeInPx
+
+ paragraph.layout(ParagraphConstraints(width))
+
+ for (i in 0..text.length) {
+ assertThat(paragraph.getBidiRunDirection(i), equalTo(TextDirection.Ltr))
+ }
+ }
+ }
+
+ @Test
+ fun getBidiRunDirection_ltr_singleLine_textDirectionRtl() {
+ withDensity(defaultDensity) {
+ val text = "abc"
+ val fontSize = 50.sp
+ val fontSizeInPx = fontSize.toPx().value
+ val paragraph = simpleParagraph(
+ text = text,
+ fontSize = fontSize,
+ textDirection = TextDirection.Rtl
+ )
+ val width = text.length * fontSizeInPx
+
+ paragraph.layout(ParagraphConstraints(width))
+
+ for (i in 0..text.length) {
+ assertThat(paragraph.getBidiRunDirection(i), equalTo(TextDirection.Ltr))
+ }
+ }
+ }
+
+ @Test
+ fun getBidiRunDirection_rtl_singleLine_textDirectionDefault() {
+ withDensity(defaultDensity) {
+ val text = "\u05D0\u05D1\u05D2\n"
+ val fontSize = 50.sp
+ val fontSizeInPx = fontSize.toPx().value
+ val paragraph = simpleParagraph(
+ text = text,
+ fontSize = fontSize
+ )
+ val width = text.length * fontSizeInPx
+
+ paragraph.layout(ParagraphConstraints(width))
+
+ for (i in 0 until text.length) {
+ assertThat(paragraph.getBidiRunDirection(i), equalTo(TextDirection.Rtl))
+ }
+ }
+ }
+
+ @Test
+ fun getBidiRunDirection_rtl_singleLine_textDirectionLtr() {
+ withDensity(defaultDensity) {
+ val text = "\u05D0\u05D1\u05D2\n"
+ val fontSize = 50.sp
+ val fontSizeInPx = fontSize.toPx().value
+ val paragraph = simpleParagraph(
+ text = text,
+ fontSize = fontSize,
+ textDirection = TextDirection.Ltr
+ )
+ val width = text.length * fontSizeInPx
+
+ paragraph.layout(ParagraphConstraints(width))
+
+ for (i in 0 until text.length - 1) {
+ assertThat(paragraph.getBidiRunDirection(i), equalTo(TextDirection.Rtl))
+ }
+ assertThat(paragraph.getBidiRunDirection(text.length - 1), equalTo(TextDirection.Ltr))
+ }
+ }
+
+ @Test
+ fun getBidiRunDirection_Bidi_singleLine_textDirectionDefault() {
+ withDensity(defaultDensity) {
+ val ltrText = "abc"
+ val rtlText = "\u05D0\u05D1\u05D2"
+ val text = ltrText + rtlText
+ val fontSize = 50.sp
+ val fontSizeInPx = fontSize.toPx().value
+ val paragraph = simpleParagraph(
+ text = text,
+ fontSize = fontSize
+ )
+ val width = text.length * fontSizeInPx
+
+ paragraph.layout(ParagraphConstraints(width))
+
+ for (i in 0 until ltrText.length) {
+ assertThat(paragraph.getBidiRunDirection(i), equalTo(TextDirection.Ltr))
+ }
+
+ for (i in ltrText.length until text.length) {
+ assertThat(paragraph.getBidiRunDirection(i), equalTo(TextDirection.Rtl))
+ }
+ }
+ }
+
+ @Test
+ fun getBidiRunDirection_Bidi_singleLine_textDirectionLtr() {
+ withDensity(defaultDensity) {
+ val ltrText = "abc"
+ val rtlText = "\u05D0\u05D1\u05D2"
+ val text = ltrText + rtlText
+ val fontSize = 50.sp
+ val fontSizeInPx = fontSize.toPx().value
+ val paragraph = simpleParagraph(
+ text = text,
+ fontSize = fontSize,
+ textDirection = TextDirection.Ltr
+ )
+ val width = text.length * fontSizeInPx
+
+ paragraph.layout(ParagraphConstraints(width))
+
+ for (i in 0 until ltrText.length) {
+ assertThat(paragraph.getBidiRunDirection(i), equalTo(TextDirection.Ltr))
+ }
+
+ for (i in ltrText.length until text.length) {
+ assertThat(paragraph.getBidiRunDirection(i), equalTo(TextDirection.Rtl))
+ }
+ }
+ }
+
+ @Test
+ fun getBidiRunDirection_Bidi_singleLine_textDirectionRtl() {
+ withDensity(defaultDensity) {
+ val ltrText = "abc"
+ val rtlText = "\u05D0\u05D1\u05D2"
+ val text = ltrText + rtlText
+ val fontSize = 50.sp
+ val fontSizeInPx = fontSize.toPx().value
+ val paragraph = simpleParagraph(
+ text = text,
+ fontSize = fontSize,
+ textDirection = TextDirection.Rtl
+ )
+ val width = text.length * fontSizeInPx
+
+ paragraph.layout(ParagraphConstraints(width))
+
+ for (i in 0 until ltrText.length) {
+ assertThat(paragraph.getBidiRunDirection(i), equalTo(TextDirection.Ltr))
+ }
+
+ for (i in ltrText.length until text.length) {
+ assertThat(paragraph.getBidiRunDirection(i), equalTo(TextDirection.Rtl))
+ }
+ }
+ }
+
+ @Test
fun locale_withCJK_shouldNotDrawSame() {
withDensity(defaultDensity) {
val text = "\u82B1"
@@ -1000,10 +1584,10 @@
val fontSizeInPx = fontSize.toPx().value
val locales = arrayOf(
// duplicate ja is on purpose
- Locale(_languageCode = "ja"),
- Locale(_languageCode = "ja"),
- Locale(_languageCode = "zh", _countryCode = "CN"),
- Locale(_languageCode = "zh", _countryCode = "TW")
+ Locale("ja"),
+ Locale("ja"),
+ Locale("zh", "CN"),
+ Locale("zh", "TW")
)
val bitmaps = locales.map { locale ->
@@ -1115,6 +1699,51 @@
}
@Test
+ fun maxLines_paintDifferently() {
+ withDensity(defaultDensity) {
+ val text = "a\na\na"
+ val fontSize = 100.sp
+ val fontSizeInPx = fontSize.toPx().value
+ val maxLines = 1
+
+ val paragraphWithMaxLine = simpleParagraph(
+ text = text,
+ fontSize = fontSize,
+ maxLines = maxLines
+ )
+ paragraphWithMaxLine.layout(ParagraphConstraints(width = fontSizeInPx))
+
+ val paragraphNoMaxLine = simpleParagraph(
+ text = text,
+ fontSize = fontSize
+ )
+ paragraphNoMaxLine.layout(ParagraphConstraints(width = fontSizeInPx))
+
+ // Make sure the maxLine is applied correctly
+ assertThat(paragraphNoMaxLine.height, greaterThan(paragraphWithMaxLine.height))
+
+ val imageNoMaxLine = Image(
+ paragraphNoMaxLine.width.roundToInt(),
+ paragraphNoMaxLine.height.roundToInt(),
+ ImageConfig.Argb8888
+ )
+ // Same size with imageNoMaxLine for comparison
+ val imageWithMaxLine = Image(
+ paragraphNoMaxLine.width.roundToInt(),
+ paragraphNoMaxLine.height.roundToInt(),
+ ImageConfig.Argb8888
+ )
+
+ paragraphNoMaxLine.paint(Canvas(imageNoMaxLine))
+ paragraphWithMaxLine.paint(Canvas(imageWithMaxLine))
+ assertThat(
+ imageNoMaxLine.nativeImage,
+ not(equalToBitmap(imageWithMaxLine.nativeImage))
+ )
+ }
+ }
+
+ @Test
fun didExceedMaxLines_withMaxLinesSmallerThanTextLines_returnsTrue() {
val text = "aaa\naa"
val maxLines = text.lines().size - 1
@@ -2496,7 +3125,7 @@
fun paint_throws_exception_if_layout_is_not_called() {
val paragraph = simpleParagraph()
- paragraph.paint(mock())
+ paragraph.paint(mock(Canvas::class.java))
}
@Test(expected = IllegalStateException::class)
diff --git a/ui/ui-text/src/androidTest/java/androidx/ui/text/TextPainterIntegrationTest.kt b/ui/ui-text/src/androidTest/java/androidx/ui/text/TextDelegateIntegrationTest.kt
similarity index 74%
rename from ui/ui-text/src/androidTest/java/androidx/ui/text/TextPainterIntegrationTest.kt
rename to ui/ui-text/src/androidTest/java/androidx/ui/text/TextDelegateIntegrationTest.kt
index 3b17aae..2c0ffe6 100644
--- a/ui/ui-text/src/androidTest/java/androidx/ui/text/TextPainterIntegrationTest.kt
+++ b/ui/ui-text/src/androidTest/java/androidx/ui/text/TextDelegateIntegrationTest.kt
@@ -27,7 +27,6 @@
import androidx.ui.core.sp
import androidx.ui.core.withDensity
import androidx.ui.engine.geometry.Rect
-import androidx.ui.engine.geometry.Size
import androidx.ui.text.FontTestData.Companion.BASIC_MEASURE_FONT
import androidx.ui.text.style.TextDirection
import androidx.ui.text.font.asFontFamily
@@ -45,37 +44,21 @@
@RunWith(JUnit4::class)
@SmallTest
-class TextPainterIntegrationTest {
+class TextDelegateIntegrationTest {
private val fontFamily = BASIC_MEASURE_FONT.asFontFamily()
private val density = Density(density = 1f)
private val context = InstrumentationRegistry.getInstrumentation().context
private val resourceLoader = TestFontResourceLoader(context)
- @Test
- fun preferredLineHeight_style_set() {
- withDensity(density) {
- val fontSize = 20.sp
- val textStyle = TextStyle(fontSize = fontSize, fontFamily = fontFamily)
- val textPainter = TextPainter(
- style = textStyle,
- density = density,
- resourceLoader = resourceLoader
- )
- val preferredHeight = textPainter.preferredLineHeight
-
- assertThat(preferredHeight).isEqualTo(fontSize.toPx().value)
- }
- }
-
// TODO(Migration/qqd): The default font size should be 14.0 but it returns 15.0. Need further
// investigation. It is being changed in the native level, and probably related to the font.
// @Test
// fun preferredLineHeight_style_not_set() {
// val defaultTextStyle = TextStyle(fontFamily = fontFamily)
-// val textPainter = TextPainter(style = defaultTextStyle)
+// val textDelegate = TextDelegate(style = defaultTextStyle)
//
-// val prefferedHeight = textPainter.preferredLineHeight
+// val prefferedHeight = textDelegate.preferredLineHeight
//
// assertThat(prefferedHeight).isEqualTo(14.0)
// }
@@ -89,16 +72,16 @@
text = text,
textStyles = listOf(AnnotatedString.Item(textStyle, 0, text.length))
)
- val textPainter = TextPainter(
+ val textDelegate = TextDelegate(
text = annotatedString,
paragraphStyle = ParagraphStyle(textDirection = TextDirection.Rtl),
density = density,
resourceLoader = resourceLoader
)
- textPainter.layout(Constraints())
+ textDelegate.layout(Constraints())
- assertThat(textPainter.minIntrinsicWidth).isEqualTo(0.0f)
+ assertThat(textDelegate.minIntrinsicWidth).isEqualTo(0.0f)
}
@Test
@@ -117,16 +100,18 @@
)
)
)
- val textPainter = TextPainter(
+ val textDelegate = TextDelegate(
text = annotatedString,
paragraphStyle = ParagraphStyle(textDirection = TextDirection.Rtl),
density = density,
resourceLoader = resourceLoader
)
- textPainter.layout(Constraints())
+ textDelegate.layout(Constraints())
- assertThat(textPainter.maxIntrinsicWidth).isEqualTo(fontSize.toPx().value * text.length)
+ assertThat(
+ textDelegate.maxIntrinsicWidth).isEqualTo(fontSize.toPx().value * text.length
+ )
}
}
@@ -146,16 +131,16 @@
)
)
)
- val textPainter = TextPainter(
+ val textDelegate = TextDelegate(
text = annotatedString,
paragraphStyle = ParagraphStyle(textDirection = TextDirection.Rtl),
density = density,
resourceLoader = resourceLoader
)
- textPainter.layout(Constraints(0.ipx, 200.ipx))
+ textDelegate.layout(Constraints(0.ipx, 200.ipx))
- assertThat(textPainter.width).isEqualTo(fontSize.toPx().value * text.length)
+ assertThat(textDelegate.width).isEqualTo(fontSize.toPx().value * text.length)
}
}
@@ -168,16 +153,16 @@
text = text,
textStyles = listOf(AnnotatedString.Item(textStyle, 0, text.length))
)
- val textPainter = TextPainter(
+ val textDelegate = TextDelegate(
text = annotatedString,
paragraphStyle = ParagraphStyle(textDirection = TextDirection.Rtl),
density = density,
resourceLoader = resourceLoader
)
- textPainter.layout(Constraints(maxWidth = width))
+ textDelegate.layout(Constraints(maxWidth = width))
- assertThat(textPainter.width).isEqualTo(width.value.toFloat())
+ assertThat(textDelegate.width).isEqualTo(width.value.toFloat())
}
@Test
@@ -196,101 +181,31 @@
)
)
)
- val textPainter = TextPainter(
+ val textDelegate = TextDelegate(
text = annotatedString,
paragraphStyle = ParagraphStyle(textDirection = TextDirection.Rtl),
density = density,
resourceLoader = resourceLoader
)
- textPainter.layout(Constraints())
+ textDelegate.layout(Constraints())
- assertThat(textPainter.height).isEqualTo(fontSize.toPx().value)
+ assertThat(textDelegate.height).isEqualTo(fontSize.toPx().value)
}
}
@Test
- fun size_getter() {
- withDensity(density) {
- val fontSize = 20.sp
- val fontSizeInPx = fontSize.toPx().value
- val text = "Hello"
- val textStyle = TextStyle(fontSize = fontSize, fontFamily = fontFamily)
- val annotatedString = AnnotatedString(
- text = text,
- textStyles = listOf(
- AnnotatedString.Item(
- textStyle,
- 0,
- text.length
- )
- )
- )
- val textPainter = TextPainter(
- text = annotatedString,
- paragraphStyle = ParagraphStyle(textDirection = TextDirection.Rtl),
- density = density,
- resourceLoader = resourceLoader
- )
-
- textPainter.layout(Constraints())
-
- assertThat(textPainter.size).isEqualTo(
- Size(
- width = fontSizeInPx * text.length,
- height = fontSizeInPx
- )
- )
- }
- }
-
- @Test
- fun didExceedMaxLines_exceed() {
- var text = ""
- for (i in 1..50) text += " Hello"
- val annotatedString = AnnotatedString(text = text)
- val textPainter = TextPainter(
- text = annotatedString,
- paragraphStyle = ParagraphStyle(textDirection = TextDirection.Rtl),
- maxLines = 2,
- density = density,
- resourceLoader = resourceLoader
- )
-
- textPainter.layout(Constraints(0.ipx, 200.ipx))
-
- assertThat(textPainter.didExceedMaxLines).isTrue()
- }
-
- @Test
- fun didExceedMaxLines_not_exceed() {
- val text = "Hello"
- val annotatedString = AnnotatedString(text = text)
- val textPainter = TextPainter(
- text = annotatedString,
- paragraphStyle = ParagraphStyle(textDirection = TextDirection.Rtl),
- maxLines = 2,
- density = density,
- resourceLoader = resourceLoader
- )
-
- textPainter.layout(Constraints(0.ipx, 200.ipx))
-
- assertThat(textPainter.didExceedMaxLines).isFalse()
- }
-
- @Test
- fun layout_build_paragraph() {
- val textPainter = TextPainter(
+ fun layout_build_layoutResult() {
+ val textDelegate = TextDelegate(
text = AnnotatedString(text = "Hello"),
paragraphStyle = ParagraphStyle(textDirection = TextDirection.Ltr),
density = density,
resourceLoader = resourceLoader
)
- textPainter.layout(Constraints(0.ipx, 20.ipx))
+ textDelegate.layout(Constraints(0.ipx, 20.ipx))
- assertThat(textPainter.multiParagraph).isNotNull()
+ assertThat(textDelegate.layoutResult).isNotNull()
}
@Test
@@ -306,15 +221,15 @@
)
)
)
- val textPainter = TextPainter(
+ val textDelegate = TextDelegate(
text = annotatedString,
paragraphStyle = ParagraphStyle(textDirection = TextDirection.Ltr),
density = density,
resourceLoader = resourceLoader
)
- textPainter.layout(Constraints())
+ textDelegate.layout(Constraints())
- val selection = textPainter.getOffsetForPosition(PxPosition.Origin)
+ val selection = textDelegate.getOffsetForPosition(PxPosition.Origin)
assertThat(selection).isEqualTo(0)
}
@@ -335,15 +250,15 @@
)
)
)
- val textPainter = TextPainter(
+ val textDelegate = TextDelegate(
text = annotatedString,
paragraphStyle = ParagraphStyle(textDirection = TextDirection.Ltr),
density = density,
resourceLoader = resourceLoader
)
- textPainter.layout(Constraints())
+ textDelegate.layout(Constraints())
- val selection = textPainter.getOffsetForPosition(
+ val selection = textDelegate.getOffsetForPosition(
position = PxPosition((fontSize.toPx().value * characterIndex + 1).px, 0.px)
)
@@ -359,16 +274,16 @@
text = text,
textStyles = listOf(AnnotatedString.Item(textStyle, 0, text.length))
)
- val textPainter = TextPainter(
+ val textDelegate = TextDelegate(
text = annotatedString,
paragraphStyle = ParagraphStyle(textDirection = TextDirection.Ltr),
density = density,
resourceLoader = resourceLoader
)
- textPainter.layout(Constraints())
+ textDelegate.layout(Constraints())
- assertThat(textPainter.hasVisualOverflow).isFalse()
+ assertThat(textDelegate.hasVisualOverflow).isFalse()
}
@Test
@@ -382,7 +297,7 @@
text = text,
textStyles = listOf(AnnotatedString.Item(textStyle, 0, text.length))
)
- val textPainter = TextPainter(
+ val textDelegate = TextDelegate(
text = annotatedString,
paragraphStyle = ParagraphStyle(textDirection = TextDirection.Ltr),
overflow = TextOverflow.Fade,
@@ -392,9 +307,9 @@
resourceLoader = resourceLoader
)
- textPainter.layout(Constraints(maxWidth = 100.ipx))
+ textDelegate.layout(Constraints(maxWidth = 100.ipx))
- assertThat(textPainter.hasVisualOverflow).isTrue()
+ assertThat(textDelegate.hasVisualOverflow).isTrue()
}
@Test
@@ -408,7 +323,7 @@
text = text,
textStyles = listOf(AnnotatedString.Item(textStyle, 0, text.length))
)
- val textPainter = TextPainter(
+ val textDelegate = TextDelegate(
text = annotatedString,
paragraphStyle = ParagraphStyle(textDirection = TextDirection.Ltr),
overflow = TextOverflow.Fade,
@@ -417,9 +332,9 @@
resourceLoader = resourceLoader
)
- textPainter.layout(Constraints(maxWidth = 100.ipx))
+ textDelegate.layout(Constraints(maxWidth = 100.ipx))
- assertThat(textPainter.hasVisualOverflow).isTrue()
+ assertThat(textDelegate.hasVisualOverflow).isTrue()
}
@Test
@@ -440,17 +355,17 @@
)
)
)
- val textPainter = TextPainter(
+ val textDelegate = TextDelegate(
text = annotatedString,
paragraphStyle = ParagraphStyle(textDirection = TextDirection.Ltr),
density = density,
resourceLoader = resourceLoader
)
- textPainter.layout(Constraints(maxWidth = 120.ipx))
+ textDelegate.layout(Constraints(maxWidth = 120.ipx))
val expectedBitmap = Bitmap.createBitmap(
- ceil(textPainter.width).toInt(),
- ceil(textPainter.height).toInt(),
+ ceil(textDelegate.width).toInt(),
+ ceil(textDelegate.height).toInt(),
Bitmap.Config.ARGB_8888
)
val expectedCanvas =
@@ -459,10 +374,10 @@
val defaultSelectionColor = Color(0x6633B5E5)
expectedPaint.color = defaultSelectionColor
- val firstLineLeft = textPainter.multiParagraph?.getLineLeft(0)
- val secondLineLeft = textPainter.multiParagraph?.getLineLeft(1)
- val firstLineRight = textPainter.multiParagraph?.getLineRight(0)
- val secondLineRight = textPainter.multiParagraph?.getLineRight(1)
+ val firstLineLeft = textDelegate.layoutResult?.multiParagraph?.getLineLeft(0)
+ val secondLineLeft = textDelegate.layoutResult?.multiParagraph?.getLineLeft(1)
+ val firstLineRight = textDelegate.layoutResult?.multiParagraph?.getLineRight(0)
+ val secondLineRight = textDelegate.layoutResult?.multiParagraph?.getLineRight(1)
expectedCanvas.drawRect(
Rect(firstLineLeft!!, 0f, firstLineRight!!, fontSizeInPx),
expectedPaint
@@ -472,21 +387,21 @@
secondLineLeft!!,
fontSizeInPx,
secondLineRight!!,
- textPainter.multiParagraph!!.height
+ textDelegate.layoutResult!!.multiParagraph.height
),
expectedPaint
)
val actualBitmap = Bitmap.createBitmap(
- ceil(textPainter.width).toInt(),
- ceil(textPainter.height).toInt(),
+ ceil(textDelegate.width).toInt(),
+ ceil(textDelegate.height).toInt(),
Bitmap.Config.ARGB_8888
)
val actualCanvas = Canvas(android.graphics.Canvas(actualBitmap))
// Run.
// Select all.
- textPainter.paintBackground(
+ textDelegate.paintBackground(
start = 0,
end = text.length,
color = defaultSelectionColor,
@@ -518,17 +433,17 @@
)
)
)
- val textPainter = TextPainter(
+ val textDelegate = TextDelegate(
text = annotatedString,
paragraphStyle = ParagraphStyle(textDirection = TextDirection.Ltr),
density = density,
resourceLoader = resourceLoader
)
- textPainter.layout(Constraints())
+ textDelegate.layout(Constraints())
val expectedBitmap = Bitmap.createBitmap(
- ceil(textPainter.width).toInt(),
- ceil(textPainter.height).toInt(),
+ ceil(textDelegate.width).toInt(),
+ ceil(textDelegate.height).toInt(),
Bitmap.Config.ARGB_8888
)
val expectedCanvas =
@@ -547,14 +462,14 @@
)
val actualBitmap = Bitmap.createBitmap(
- ceil(textPainter.width).toInt(),
- ceil(textPainter.height).toInt(),
+ ceil(textDelegate.width).toInt(),
+ ceil(textDelegate.height).toInt(),
Bitmap.Config.ARGB_8888
)
val actualCanvas = Canvas(android.graphics.Canvas(actualBitmap))
// Run.
- textPainter.paintBackground(
+ textDelegate.paintBackground(
start = selectionStart,
end = selectionEnd,
color = defaultSelectionColor,
@@ -589,17 +504,17 @@
)
)
)
- val textPainter = TextPainter(
+ val textDelegate = TextDelegate(
text = annotatedString,
paragraphStyle = ParagraphStyle(textDirection = TextDirection.Ltr),
density = density,
resourceLoader = resourceLoader
)
- textPainter.layout(Constraints())
+ textDelegate.layout(Constraints())
val expectedBitmap = Bitmap.createBitmap(
- ceil(textPainter.width).toInt(),
- ceil(textPainter.height).toInt(),
+ ceil(textDelegate.width).toInt(),
+ ceil(textDelegate.height).toInt(),
Bitmap.Config.ARGB_8888
)
val expectedCanvas =
@@ -630,14 +545,14 @@
)
val actualBitmap = Bitmap.createBitmap(
- ceil(textPainter.width).toInt(),
- ceil(textPainter.height).toInt(),
+ ceil(textDelegate.width).toInt(),
+ ceil(textDelegate.height).toInt(),
Bitmap.Config.ARGB_8888
)
val actualCanvas = Canvas(android.graphics.Canvas(actualBitmap))
// Run.
- textPainter.paintBackground(
+ textDelegate.paintBackground(
start = selectionLTRStart,
end = textLTR.length + selectionRTLEnd,
color = defaultSelectionColor,
@@ -670,17 +585,17 @@
)
)
val selectionColor = Color(0x66AABB33)
- val textPainter = TextPainter(
+ val textDelegate = TextDelegate(
text = annotatedString,
paragraphStyle = ParagraphStyle(textDirection = TextDirection.Ltr),
density = density,
resourceLoader = resourceLoader
)
- textPainter.layout(Constraints())
+ textDelegate.layout(Constraints())
val expectedBitmap = Bitmap.createBitmap(
- ceil(textPainter.width).toInt(),
- ceil(textPainter.height).toInt(),
+ ceil(textDelegate.width).toInt(),
+ ceil(textDelegate.height).toInt(),
Bitmap.Config.ARGB_8888
)
val expectedCanvas =
@@ -698,14 +613,14 @@
)
val actualBitmap = Bitmap.createBitmap(
- ceil(textPainter.width).toInt(),
- ceil(textPainter.height).toInt(),
+ ceil(textDelegate.width).toInt(),
+ ceil(textDelegate.height).toInt(),
Bitmap.Config.ARGB_8888
)
val actualCanvas = Canvas(android.graphics.Canvas(actualBitmap))
// Run.
- textPainter.paintBackground(
+ textDelegate.paintBackground(
start = selectionStart,
end = selectionEnd,
color = selectionColor,
diff --git a/ui/ui-text/src/androidTest/java/androidx/ui/text/platform/AndroidParagraphTest.kt b/ui/ui-text/src/androidTest/java/androidx/ui/text/platform/AndroidParagraphTest.kt
index ef5b100..3be82b7 100644
--- a/ui/ui-text/src/androidTest/java/androidx/ui/text/platform/AndroidParagraphTest.kt
+++ b/ui/ui-text/src/androidTest/java/androidx/ui/text/platform/AndroidParagraphTest.kt
@@ -37,7 +37,6 @@
import androidx.ui.text.style.TextIndent
import androidx.ui.text.font.FontFamily
import androidx.ui.text.font.asFontFamily
-import androidx.ui.text.Locale
import androidx.ui.graphics.Color
import androidx.ui.text.matchers.equalToBitmap
import androidx.ui.text.matchers.hasSpan
@@ -63,6 +62,7 @@
import org.junit.Test
import org.junit.runner.RunWith
import org.junit.runners.JUnit4
+import java.util.Locale
import kotlin.math.ceil
@RunWith(JUnit4::class)
@@ -1216,7 +1216,7 @@
@Test
fun locale_isSetOnParagraphImpl_enUS() {
- val locale = Locale(_languageCode = "en", _countryCode = "US")
+ val locale = Locale("en", "US")
val text = "abc"
val paragraph = simpleParagraph(
text = text,
@@ -1230,7 +1230,7 @@
@Test
fun locale_isSetOnParagraphImpl_jpJP() {
- val locale = Locale(_languageCode = "ja", _countryCode = "JP")
+ val locale = Locale("ja", "JP")
val text = "abc"
val paragraph = simpleParagraph(
text = text,
@@ -1244,7 +1244,7 @@
@Test
fun locale_noCountryCode_isSetOnParagraphImpl() {
- val locale = Locale(_languageCode = "ja")
+ val locale = Locale("ja")
val text = "abc"
val paragraph = simpleParagraph(
text = text,
diff --git a/ui/ui-text/src/main/java/androidx/ui/input/EditorStyle.kt b/ui/ui-text/src/main/java/androidx/ui/input/EditorStyle.kt
new file mode 100644
index 0000000..a4c2dce
--- /dev/null
+++ b/ui/ui-text/src/main/java/androidx/ui/input/EditorStyle.kt
@@ -0,0 +1,43 @@
+/*
+ * 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.input
+
+import androidx.ui.graphics.Color
+import androidx.ui.text.TextStyle
+
+/**
+ * Data class holding text display attributes used for editors.
+ */
+data class EditorStyle(
+ /** The editor text style */
+ val textStyle: TextStyle? = null,
+
+ /**
+ * The composition background color
+ *
+ * @see EditorModel.composition
+ */
+ val compositionColor: Color = Color(alpha = 0xFF, red = 0xB0, green = 0xE0, blue = 0xE6),
+
+ /**
+ * The selection background color
+ *
+ * @see EditorModel.selection
+ */
+ // TODO(nona): share with Text.DEFAULT_SELECTION_COLOR
+ val selectionColor: Color = Color(alpha = 0x66, red = 0x33, green = 0xB5, blue = 0xE5)
+)
diff --git a/ui/ui-framework/src/main/java/androidx/ui/core/VisualTransformation.kt b/ui/ui-text/src/main/java/androidx/ui/input/VisualTransformation.kt
similarity index 93%
rename from ui/ui-framework/src/main/java/androidx/ui/core/VisualTransformation.kt
rename to ui/ui-text/src/main/java/androidx/ui/input/VisualTransformation.kt
index 23ff8a6..7e5377f 100644
--- a/ui/ui-framework/src/main/java/androidx/ui/core/VisualTransformation.kt
+++ b/ui/ui-text/src/main/java/androidx/ui/input/VisualTransformation.kt
@@ -14,8 +14,9 @@
* limitations under the License.
*/
-package androidx.ui.core
+package androidx.ui.input
+import androidx.annotation.RestrictTo
import androidx.ui.text.AnnotatedString
/**
@@ -128,15 +129,20 @@
*/
class PasswordVisualTransformation(val mask: Char = '\u2022') : VisualTransformation {
override fun filter(text: AnnotatedString): TransformedText {
- return TransformedText(AnnotatedString(Character.toString(mask).repeat(text.text.length)),
- identityOffsetMap)
+ return TransformedText(
+ AnnotatedString(Character.toString(mask).repeat(text.text.length)),
+ identityOffsetMap
+ )
}
}
/**
* The offset map used for identity mapping.
+ *
+ * @hide
*/
-internal val identityOffsetMap = object : OffsetMap {
+@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
+val identityOffsetMap = object : OffsetMap {
override fun originalToTransformed(offset: Int): Int = offset
override fun transformedToOriginal(offset: Int): Int = offset
}
diff --git a/ui/ui-text/src/main/java/androidx/ui/text/Locale.kt b/ui/ui-text/src/main/java/androidx/ui/text/Locale.kt
deleted file mode 100644
index 2c75c98..0000000
--- a/ui/ui-text/src/main/java/androidx/ui/text/Locale.kt
+++ /dev/null
@@ -1,207 +0,0 @@
-/*
- * 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.text
-
-/**
- * An identifier used to select a user's language and formatting preferences,
- * consisting of a language and a country. This is a subset of locale
- * identifiers as defined by BCP 47.
- *
- * Locales are canonicalized according to the "preferred value" entries in the
- * [IANA Language Subtag
- * Registry](https://www.iana.org/assignments/language-subtag-registry/language-subtag-registry).
- * For example, `const Locale('he')` and `const Locale('iw')` are equal and
- * both have the [languageCode] `he`, because `iw` is a deprecated language
- * subtag that was replaced by the subtag `he`.
- *
- * The default constructor creates a new Locale object. The first argument is the
- * primary language subtag, the second is the region subtag.
- *
- * For example:
- *
- * val swissFrench = Locale('fr', 'CH');
- * val canadianFrench = Locale('fr', 'CA');
- *
- *
- * The primary language subtag must not be null. The region subtag is
- * optional.
- *
- * The values are _case sensitive_, and should match the case of the relevant
- * subtags in the [IANA Language Subtag Registry]
- * (https://www.iana.org/assignments/language-subtag-registry/language-subtag-registry).
- * Typically this means the primary language subtag should be lowercase and
- * the region subtag should be uppercase.
- */
-data class Locale(
- val _languageCode: String,
- val _countryCode: String? = null
-) {
-
- // TODO(Migration/siyamed): this class is far far far too much very limited.
- // we might want to wrap system locale, or use system locale.
- // The package is also not good, Locale should be somewhere else not under window.
- // nona@ we need at least
- // * ISO 15924 script tag. This is required for sr-Latn/sr-Cyrl support.
- // * Variant. This is required for hyphenation for German, de-1996/de-1901
- // * Unicode extensions: This is required Arabic/Latin digits selection.
- // Also, this needs to be a list of locale, otherwise
- // * locale list based font fallback doesn't work.
- // * resource fallback may not work well.
-
- /**
- * The primary language subtag for the locale.
- *
- * This must not be null.
- *
- * This is expected to be string registered in the [IANA Language Subtag
- * Registry](https://www.iana.org/assignments/language-subtag-registry/language-subtag-registry)
- * with the type "language". The string specified must match the case of the
- * string in the registry.
- *
- * Language subtags that are deprecated in the registry and have a preferred
- * code are changed to their preferred code. For example, `const
- * Locale('he')` and `const Locale('iw')` are equal, and both have the
- * [languageCode] `he`, because `iw` is a deprecated language subtag that was
- * replaced by the subtag `he`.
- */
- val languageCode: String
- get() = _canonicalizeLanguageCode(_languageCode)
-
- /**
- * The region subtag for the locale.
- *
- * This can be null.
- *
- * This is expected to be string registered in the [IANA Language Subtag
- * Registry](https://www.iana.org/assignments/language-subtag-registry/language-subtag-registry)
- * with the type "region". The string specified must match the case of the
- * string in the registry.
- *
- * Region subtags that are deprecated in the registry and have a preferred
- * code are changed to their preferred code. For example, `const Locale('de',
- * 'DE')` and `const Locale('de', 'DD')` are equal, and both have the
- * [countryCode] `DE`, because `DD` is a deprecated language subtag that was
- * replaced by the subtag `DE`.
- */
- val countryCode
- get() = if (_countryCode != null) _canonicalizeRegionCode(
- _countryCode
- ) else null
-
- companion object {
- fun _canonicalizeLanguageCode(languageCode: String): String {
- // This switch statement is generated by //flutter/tools/gen_locale.dart
- // TODO(popam): look into that tool
- // Mappings generated for language subtag registry as of 2017-08-15.
- return when (languageCode) {
- "in" -> "id"; // Indonesian; deprecated 1989-01-01
- "iw" -> "he"; // Hebrew; deprecated 1989-01-01
- "ji" -> "yi"; // Yiddish; deprecated 1989-01-01
- "jw" -> "jv"; // Javanese; deprecated 2001-08-13
- "mo" -> "ro"; // Moldavian, Moldovan; deprecated 2008-11-22
- "aam" -> "aas"; // Aramanik; deprecated 2015-02-12
- "adp" -> "dz"; // Adap; deprecated 2015-02-12
- "aue" -> "ktz"; // =/Kx"au//"ein; deprecated 2015-02-12
- "ayx" -> "nun"; // Ayi (China); deprecated 2011-08-16
- "bgm" -> "bcg"; // Baga Mboteni; deprecated 2016-05-30
- "bjd" -> "drl"; // Bandjigali; deprecated 2012-08-12
- "ccq" -> "rki"; // Chaungtha; deprecated 2012-08-12
- "cjr" -> "mom"; // Chorotega; deprecated 2010-03-11
- "cka" -> "cmr"; // Khumi Awa Chin; deprecated 2012-08-12
- "cmk" -> "xch"; // Chimakum; deprecated 2010-03-11
- "coy" -> "pij"; // Coyaima; deprecated 2016-05-30
- "cqu" -> "quh"; // Chilean Quechua; deprecated 2016-05-30
- "drh" -> "khk"; // Darkhat; deprecated 2010-03-11
- "drw" -> "prs"; // Darwazi; deprecated 2010-03-11
- "gav" -> "dev"; // Gabutamon; deprecated 2010-03-11
- "gfx" -> "vaj"; // Mangetti Dune !Xung; deprecated 2015-02-12
- "ggn" -> "gvr"; // Eastern Gurung; deprecated 2016-05-30
- "gti" -> "nyc"; // Gbati-ri; deprecated 2015-02-12
- "guv" -> "duz"; // Gey; deprecated 2016-05-30
- "hrr" -> "jal"; // Horuru; deprecated 2012-08-12
- "ibi" -> "opa"; // Ibilo; deprecated 2012-08-12
- "ilw" -> "gal"; // Talur; deprecated 2013-09-10
- "jeg" -> "oyb"; // Jeng; deprecated 2017-02-23
- "kgc" -> "tdf"; // Kasseng; deprecated 2016-05-30
- "kgh" -> "kml"; // Upper Tanudan Kalinga; deprecated 2012-08-12
- "koj" -> "kwv"; // Sara Dunjo; deprecated 2015-02-12
- "krm" -> "bmf"; // Krim; deprecated 2017-02-23
- "ktr" -> "dtp"; // Kota Marudu Tinagas; deprecated 2016-05-30
- "kvs" -> "gdj"; // Kunggara; deprecated 2016-05-30
- "kwq" -> "yam"; // Kwak; deprecated 2015-02-12
- "kxe" -> "tvd"; // Kakihum; deprecated 2015-02-12
- "kzj" -> "dtp"; // Coastal Kadazan; deprecated 2016-05-30
- "kzt" -> "dtp"; // Tambunan Dusun; deprecated 2016-05-30
- "lii" -> "raq"; // Lingkhim; deprecated 2015-02-12
- "lmm" -> "rmx"; // Lamam; deprecated 2014-02-28
- "meg" -> "cir"; // Mea; deprecated 2013-09-10
- "mst" -> "mry"; // Cataelano Mandaya; deprecated 2010-03-11
- "mwj" -> "vaj"; // Maligo; deprecated 2015-02-12
- "myt" -> "mry"; // Sangab Mandaya; deprecated 2010-03-11
- "nad" -> "xny"; // Nijadali; deprecated 2016-05-30
- "nnx" -> "ngv"; // Ngong; deprecated 2015-02-12
- "nts" -> "pij"; // Natagaimas; deprecated 2016-05-30
- "oun" -> "vaj"; // !O!ung; deprecated 2015-02-12
- "pcr" -> "adx"; // Panang; deprecated 2013-09-10
- "pmc" -> "huw"; // Palumata; deprecated 2016-05-30
- "pmu" -> "phr"; // Mirpur Panjabi; deprecated 2015-02-12
- "ppa" -> "bfy"; // Pao; deprecated 2016-05-30
- "ppr" -> "lcq"; // Piru; deprecated 2013-09-10
- "pry" -> "prt"; // Pray 3; deprecated 2016-05-30
- "puz" -> "pub"; // Purum Naga; deprecated 2014-02-28
- "sca" -> "hle"; // Sansu; deprecated 2012-08-12
- "skk" -> "oyb"; // Sok; deprecated 2017-02-23
- "tdu" -> "dtp"; // Tempasuk Dusun; deprecated 2016-05-30
- "thc" -> "tpo"; // Tai Hang Tong; deprecated 2016-05-30
- "thx" -> "oyb"; // The; deprecated 2015-02-12
- "tie" -> "ras"; // Tingal; deprecated 2011-08-16
- "tkk" -> "twm"; // Takpa; deprecated 2011-08-16
- "tlw" -> "weo"; // South Wemale; deprecated 2012-08-12
- "tmp" -> "tyj"; // Tai Mène; deprecated 2016-05-30
- "tne" -> "kak"; // Tinoc Kallahan; deprecated 2016-05-30
- "tnf" -> "prs"; // Tangshewi; deprecated 2010-03-11
- "tsf" -> "taj"; // Southwestern Tamang; deprecated 2015-02-12
- "uok" -> "ema"; // Uokha; deprecated 2015-02-12
- "xba" -> "cax"; // Kamba (Brazil); deprecated 2016-05-30
- "xia" -> "acn"; // Xiandao; deprecated 2013-09-10
- "xkh" -> "waw"; // Karahawyana; deprecated 2016-05-30
- "xsj" -> "suj"; // Subi; deprecated 2015-02-12
- "ybd" -> "rki"; // Yangbye; deprecated 2012-08-12
- "yma" -> "lrr"; // Yamphe; deprecated 2012-08-12
- "ymt" -> "mtm"; // Mator-Taygi-Karagas; deprecated 2015-02-12
- "yos" -> "zom"; // Yos; deprecated 2013-09-10
- "yuu" -> "yug"; // Yugh; deprecated 2014-02-28
- else -> languageCode
- }
- }
-
- fun _canonicalizeRegionCode(regionCode: String): String {
- // This switch statement is generated by //flutter/tools/gen_locale.dart
- // TODO(popam): look into that tool
- // Mappings generated for language subtag registry as of 2017-08-15.
- return when (regionCode) {
- "BU" -> "MM"; // Burma; deprecated 1989-12-05
- "DD" -> "DE"; // German Democratic Republic; deprecated 1990-10-30
- "FX" -> "FR"; // Metropolitan France; deprecated 1997-07-14
- "TP" -> "TL"; // East Timor; deprecated 2002-05-20
- "YD" -> "YE"; // Democratic Yemen; deprecated 1990-08-14
- "ZR" -> "CD"; // Zaire; deprecated 1997-07-14
- else -> regionCode
- }
- }
- }
-}
\ No newline at end of file
diff --git a/ui/ui-text/src/main/java/androidx/ui/text/MultiParagraph.kt b/ui/ui-text/src/main/java/androidx/ui/text/MultiParagraph.kt
index e7923f0..f41a7b1 100644
--- a/ui/ui-text/src/main/java/androidx/ui/text/MultiParagraph.kt
+++ b/ui/ui-text/src/main/java/androidx/ui/text/MultiParagraph.kt
@@ -25,6 +25,7 @@
import androidx.ui.painting.Canvas
import androidx.ui.painting.Path
import androidx.ui.text.font.Font
+import androidx.ui.text.style.TextDirection
import java.lang.IllegalStateException
import kotlin.math.max
@@ -268,6 +269,67 @@
}
}
+ /** Get the secondary horizontal position for the specified text offset. */
+ fun getSecondaryHorizontal(offset: Int): Float {
+ assertNeedLayout()
+ if (offset !in 0..annotatedString.text.length) {
+ throw AssertionError("offset($offset) is out of bounds " +
+ "(0,${annotatedString.text.length}")
+ }
+
+ val paragraphIndex = if (offset == annotatedString.text.length) {
+ paragraphInfoList.lastIndex
+ } else {
+ findParagraphByIndex(paragraphInfoList, offset)
+ }
+
+ return with(paragraphInfoList[paragraphIndex]) {
+ paragraph.getSecondaryHorizontal(offset.toLocalIndex())
+ }
+ }
+
+ /**
+ * Get the text direction of the paragraph containing the given offset.
+ */
+ fun getParagraphDirection(offset: Int): TextDirection {
+ assertNeedLayout()
+ if (offset !in 0..annotatedString.text.length) {
+ throw AssertionError("offset($offset) is out of bounds " +
+ "(0,${annotatedString.text.length}")
+ }
+
+ val paragraphIndex = if (offset == annotatedString.text.length) {
+ paragraphInfoList.lastIndex
+ } else {
+ findParagraphByIndex(paragraphInfoList, offset)
+ }
+
+ return with(paragraphInfoList[paragraphIndex]) {
+ paragraph.getParagraphDirection(offset.toLocalIndex())
+ }
+ }
+
+ /**
+ * Get the text direction of the character at the given offset.
+ */
+ fun getBidiRunDirection(offset: Int): TextDirection {
+ assertNeedLayout()
+ if (offset !in 0..annotatedString.text.length) {
+ throw AssertionError("offset($offset) is out of bounds " +
+ "(0,${annotatedString.text.length}")
+ }
+
+ val paragraphIndex = if (offset == annotatedString.text.length) {
+ paragraphInfoList.lastIndex
+ } else {
+ findParagraphByIndex(paragraphInfoList, offset)
+ }
+
+ return with(paragraphInfoList[paragraphIndex]) {
+ paragraph.getBidiRunDirection(offset.toLocalIndex())
+ }
+ }
+
/**
* Returns the TextRange of the word at the given character offset. Characters not
* part of a word, such as spaces, symbols, and punctuation, have word breaks
diff --git a/ui/ui-text/src/main/java/androidx/ui/text/Paragraph.kt b/ui/ui-text/src/main/java/androidx/ui/text/Paragraph.kt
index a6a8156..7ae05f2 100644
--- a/ui/ui-text/src/main/java/androidx/ui/text/Paragraph.kt
+++ b/ui/ui-text/src/main/java/androidx/ui/text/Paragraph.kt
@@ -24,6 +24,7 @@
import androidx.ui.text.font.Font
import androidx.ui.text.platform.AndroidParagraph
import androidx.ui.text.platform.TypefaceAdapter
+import androidx.ui.text.style.TextDirection
/**
* A paragraph of text.
@@ -135,6 +136,23 @@
@RestrictTo(RestrictTo.Scope.LIBRARY)
fun getPrimaryHorizontal(offset: Int): Float
+ /**
+ * Get the secondary horizontal position for the specified text offset.
+ * @hide
+ */
+ @RestrictTo(RestrictTo.Scope.LIBRARY)
+ fun getSecondaryHorizontal(offset: Int): Float
+
+ /**
+ * Get the text direction of the paragraph containing the given offset.
+ */
+ fun getParagraphDirection(offset: Int): TextDirection
+
+ /**
+ * Get the text direction of the character at the given offset.
+ */
+ fun getBidiRunDirection(offset: Int): TextDirection
+
/** Returns the character offset closest to the given graphical position. */
fun getOffsetForPosition(position: PxPosition): Int
diff --git a/ui/ui-text/src/main/java/androidx/ui/text/ParagraphConstraints.kt b/ui/ui-text/src/main/java/androidx/ui/text/ParagraphConstraints.kt
index 0de2f0d..be60281 100644
--- a/ui/ui-text/src/main/java/androidx/ui/text/ParagraphConstraints.kt
+++ b/ui/ui-text/src/main/java/androidx/ui/text/ParagraphConstraints.kt
@@ -38,8 +38,7 @@
* forced line break is placed after it (even if an explicit line break
* follows).
*
- * The width influences how ellipses are applied. See the discussion at
- * [TextPainter] for more details.
+ * The width influences how ellipses are applied.
*
* This width is also used to position glyphs according to the text alignment
* described in the [ParagraphStyle.textAlign] to create [Paragraph].
diff --git a/ui/ui-text/src/main/java/androidx/ui/text/TextPainter.kt b/ui/ui-text/src/main/java/androidx/ui/text/TextDelegate.kt
similarity index 61%
rename from ui/ui-text/src/main/java/androidx/ui/text/TextPainter.kt
rename to ui/ui-text/src/main/java/androidx/ui/text/TextDelegate.kt
index c9f865d..a58e727 100644
--- a/ui/ui-text/src/main/java/androidx/ui/text/TextPainter.kt
+++ b/ui/ui-text/src/main/java/androidx/ui/text/TextDelegate.kt
@@ -17,7 +17,7 @@
package androidx.ui.text
import androidx.annotation.RestrictTo
-import androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP
+import androidx.annotation.RestrictTo.Scope.LIBRARY
import androidx.annotation.VisibleForTesting
import androidx.ui.core.Constraints
import androidx.ui.core.Density
@@ -41,6 +41,7 @@
import androidx.ui.painting.Shader
import androidx.ui.text.font.Font
import androidx.ui.text.style.TextOverflow
+import java.util.Locale
import kotlin.math.ceil
private val DefaultTextAlign: TextAlign = TextAlign.Start
@@ -49,6 +50,20 @@
private val DefaultFontSize: Sp = 14.sp
/**
+ * Resolve text style to be able to pass to underlying paragraphs.
+ *
+ * We need to pass non-null font size to underlying paragraph.
+ */
+private fun resolveTextStyle(style: TextStyle?) =
+ if (style == null) {
+ TextStyle(fontSize = DefaultFontSize)
+ } else if (style.fontSize == null) {
+ style.copy(fontSize = DefaultFontSize)
+ } else {
+ style
+ }
+
+/**
* Unfortunately, using full precision floating point here causes bad layouts because floating
* point math isn't associative. If we add and subtract padding, for example, we'll get
* different values when we estimate sizes and when we actually compute layout because the
@@ -63,9 +78,9 @@
/**
* An object that paints a [TextSpan] tree into a [Canvas].
*
- * To use a [TextPainter], follow these steps:
+ * To use a [TextDelegate], follow these steps:
*
- * 1. Create a [TextSpan] tree and pass it to the [TextPainter] constructor.
+ * 1. Create a [TextSpan] tree and pass it to the [TextDelegate] constructor.
*
* 2. Call [layout] to prepare the paragraph.
*
@@ -80,7 +95,7 @@
*
* @param style The text style specified to render the text. Notice that you can also set text style
* on the given [AnnotatedString], and the style set on [text] always has higher priority than this
- * setting. But if only one gobal text style is needed, passing it to [TextPainter] is always
+ * setting. But if only one gobal text style is needed, passing it to [TextDelegate] is always
* preferred.
*
* @param paragraphStyle Style configuration that applies only to paragraphs such as text alignment,
@@ -101,11 +116,14 @@
* After this is set, you must call [layout] before the next call to [paint].
*
* @param locale The locale used to select region-specific glyphs.
+ *
+ * @hide
*/
-class TextPainter(
- text: AnnotatedString? = null,
- val style: TextStyle? = null,
- val paragraphStyle: ParagraphStyle? = null,
+@RestrictTo(RestrictTo.Scope.LIBRARY)
+class TextDelegate(
+ val text: AnnotatedString,
+ style: TextStyle? = null,
+ paragraphStyle: ParagraphStyle? = null,
val maxLines: Int? = null,
val softWrap: Boolean = true,
val overflow: TextOverflow = TextOverflow.Clip,
@@ -117,98 +135,55 @@
assert(maxLines == null || maxLines > 0)
}
- @VisibleForTesting
- internal var multiParagraph: MultiParagraph? = null
- private set
+ /**
+ * The resolved text style.
+ */
+ val textStyle: TextStyle = resolveTextStyle(style)
- @VisibleForTesting
- internal var needsLayout = true
- private set
+ /**
+ * The paragraph style.
+ *
+ * If null is passed to constructor, use default paragraph style.
+ */
+ val paragraphStyle: ParagraphStyle = paragraphStyle ?: ParagraphStyle()
+ /**
+ * The data class which holds text layout result.
+ */
@VisibleForTesting
- internal var layoutTemplate: Paragraph? = null
- private set
+ internal data class LayoutResult(
+ /**
+ * The multi paragraph object.
+ *
+ * The text layout is already computed.
+ */
+ val multiParagraph: MultiParagraph,
+ /**
+ * The amount of space required to paint this text.
+ */
+ val size: Size
+ )
+
+ /**
+ * The text layout result. null if text layout is not computed.
+ */
+ @VisibleForTesting
+ internal var layoutResult: LayoutResult? = null
private var overflowShader: Shader? = null
@VisibleForTesting
internal var hasVisualOverflow = false
- private set
private var lastMinWidth: Float = 0.0f
private var lastMaxWidth: Float = 0.0f
- @RestrictTo(LIBRARY_GROUP)
- var text: AnnotatedString? = text
- set(value) {
- if (field == value) return
- field = value
- multiParagraph = null
- needsLayout = true
- }
-
- internal val textStyle: TextStyle
- get() = style ?: TextStyle()
-
- internal val textAlign: TextAlign =
- if (paragraphStyle?.textAlign != null) paragraphStyle.textAlign else DefaultTextAlign
-
+ @VisibleForTesting
internal val textDirection: TextDirection? =
paragraphStyle?.textDirection ?: DefaultTextDirection
- private val isEllipsis = (overflow == TextOverflow.Ellipsis)
-
- private fun createTextStyle(): TextStyle {
- return textStyle.copy(
- fontSize = (textStyle.fontSize ?: DefaultFontSize)
- )
- }
-
- internal fun createParagraphStyle(): ParagraphStyle {
- return ParagraphStyle(
- textAlign = textAlign,
- textDirection = textDirection,
- textIndent = paragraphStyle?.textIndent,
- lineHeight = paragraphStyle?.lineHeight
- )
- }
-
- /**
- * The height of a space in [text] in logical pixels.
- *
- * Not every line of text in [text] will have this height, but this height is "typical" for
- * text in [text] and useful for sizing other objects relative a typical line of text.
- *
- * Obtaining this value does not require calling [layout].
- *
- * The style of the [text] property is used to determine the font settings that contribute to
- * the [preferredLineHeight]. If [textStyle] is null, the default [TextStyle] values are used.
- */
- val preferredLineHeight: Float
- get() {
- if (layoutTemplate == null) {
- // TODO(Migration/qqd): The textDirection below used to be RTL.
- layoutTemplate = Paragraph(
- text = " ",
- style = createTextStyle(),
- // direction doesn't matter, text is just a space
- paragraphStyle = createParagraphStyle(),
- textStyles = listOf(),
- maxLines = maxLines,
- ellipsis = isEllipsis,
- density = density,
- resourceLoader = resourceLoader
- )
- layoutTemplate?.layout(ParagraphConstraints(width = Float.POSITIVE_INFINITY))
- }
- return layoutTemplate!!.height
- }
-
- private fun assertNeedsLayout(name: String) {
- assert(!needsLayout) {
- "TextPainter.$name should only be called after layout has been called."
- }
- }
+ private inline fun <T> assumeLayout(block: (LayoutResult) -> T) =
+ block(layoutResult ?: throw AssertionError("layout must be called first"))
/**
* The width at which decreasing the width of the text would prevent it from painting itself
@@ -217,10 +192,7 @@
* Valid only after [layout] has been called.
*/
val minIntrinsicWidth: Float
- get() {
- assertNeedsLayout("minIntrinsicWidth")
- return applyFloatingPointHack(multiParagraph!!.minIntrinsicWidth)
- }
+ get() = assumeLayout { applyFloatingPointHack(it.multiParagraph.minIntrinsicWidth) }
/**
* The width at which increasing the width of the text no longer decreases the height.
@@ -228,10 +200,7 @@
* Valid only after [layout] has been called.
*/
val maxIntrinsicWidth: Float
- get() {
- assertNeedsLayout("maxIntrinsicWidth")
- return applyFloatingPointHack(multiParagraph!!.maxIntrinsicWidth)
- }
+ get() = assumeLayout { applyFloatingPointHack(it.multiParagraph.maxIntrinsicWidth) }
/**
* The horizontal space required to paint this text.
@@ -239,10 +208,7 @@
* Valid only after [layout] has been called.
*/
val width: Float
- get() {
- assertNeedsLayout("width")
- return applyFloatingPointHack(size.width)
- }
+ get() = assumeLayout { applyFloatingPointHack(it.size.width) }
/**
* The vertical space required to paint this text.
@@ -250,39 +216,7 @@
* Valid only after [layout] has been called.
*/
val height: Float
- get() {
- assertNeedsLayout("height")
- return applyFloatingPointHack(size.height)
- }
-
- /**
- * The amount of space required to paint this text.
- *
- * Valid only after [layout] has been called.
- */
- var size: Size = Size(0f, 0f)
- get() {
- assertNeedsLayout("size")
- return field
- }
- private set
-
- /**
- * Whether any text was truncated or ellipsized.
- *
- * If [maxLines] is not null, this is true if there were more lines to be drawn than the given
- * [maxLines], and thus at least one line was omitted in the output; otherwise it is false.
- *
- * If [maxLines] is null, this is true if [overflow] is [TextOverflow.Ellipsis] and there was a
- * line that overflowed the `maxWidth` argument passed to [layout]; otherwise it is false.
- *
- * Valid only after [layout] has been called.
- */
- val didExceedMaxLines: Boolean
- get() {
- assertNeedsLayout("didExceedMaxLines")
- return multiParagraph!!.didExceedMaxLines
- }
+ get() = assumeLayout { applyFloatingPointHack(it.size.height) }
/**
* Computes the visual position of the glyphs for painting the text.
@@ -292,13 +226,10 @@
*
* The [text] and [textDirection] properties must be non-null before this is called.
*/
- private fun layoutText(minWidth: Float = 0.0f, maxWidth: Float = Float.POSITIVE_INFINITY) {
- assert(text != null) {
- "TextPainter.text must be set to a non-null value before using the TextPainter."
- }
+ private fun layoutText(minWidth: Float, maxWidth: Float): MultiParagraph {
assert(textDirection != null) {
- "TextPainter.textDirection must be set to a non-null value before using the" +
- " TextPainter."
+ "TextDelegate.textDirection must be set to a non-null value before using the" +
+ " TextDelegate."
}
// TODO(haoyuchang): fix that when softWarp is false and overflow is Ellipsis, ellipsis
@@ -306,41 +237,48 @@
val widthMatters = softWrap || overflow == TextOverflow.Ellipsis
val finalMaxWidth = if (widthMatters) maxWidth else Float.POSITIVE_INFINITY
- if (!needsLayout && minWidth == lastMinWidth && finalMaxWidth == lastMaxWidth) return
- needsLayout = false
-
- if (multiParagraph == null) {
- multiParagraph = MultiParagraph(
- text!!,
- createTextStyle(),
- paragraphStyle ?: ParagraphStyle(),
- maxLines,
- isEllipsis,
- density,
- resourceLoader
- )
+ // If the layout result is the same one we computed before, just return the previous
+ // result.
+ val prevResult = layoutResult
+ if (prevResult != null && minWidth == lastMinWidth && finalMaxWidth == lastMaxWidth) {
+ return prevResult.multiParagraph
}
+
lastMinWidth = minWidth
lastMaxWidth = finalMaxWidth
- multiParagraph!!.layout(ParagraphConstraints(width = finalMaxWidth))
+ val multiParagraph = MultiParagraph(
+ annotatedString = text,
+ textStyle = textStyle,
+ paragraphStyle = paragraphStyle,
+ maxLines = maxLines,
+ ellipsis = overflow == TextOverflow.Ellipsis,
+ density = density,
+ resourceLoader = resourceLoader
+ ).apply { layout(ParagraphConstraints(width = finalMaxWidth)) }
+
if (minWidth != finalMaxWidth) {
- val newWidth = maxIntrinsicWidth.coerceIn(minWidth, finalMaxWidth)
- if (newWidth != multiParagraph!!.width) {
- multiParagraph!!.layout(ParagraphConstraints(width = newWidth))
+ val newWidth = multiParagraph.maxIntrinsicWidth.coerceIn(minWidth, finalMaxWidth)
+ if (newWidth != multiParagraph.width) {
+ multiParagraph.layout(ParagraphConstraints(width = newWidth))
}
}
+
+ return multiParagraph
}
fun layout(constraints: Constraints) {
- layoutText(constraints.minWidth.value.toFloat(), constraints.maxWidth.value.toFloat())
+ val multiParagraph = layoutText(
+ constraints.minWidth.value.toFloat(),
+ constraints.maxWidth.value.toFloat()
+ )
- val didOverflowHeight = didExceedMaxLines
- size = constraints.constrain(
- IntPxSize(multiParagraph!!.width.px.round(), multiParagraph!!.height.px.round())
+ val didOverflowHeight = multiParagraph.didExceedMaxLines
+ val size = constraints.constrain(
+ IntPxSize(multiParagraph.width.px.round(), multiParagraph.height.px.round())
).let {
Size(it.width.value.toFloat(), it.height.value.toFloat())
}
- val didOverflowWidth = size.width < multiParagraph!!.width
+ val didOverflowWidth = size.width < multiParagraph.width
// TODO(abarth): We're only measuring the sizes of the line boxes here. If
// the glyphs draw outside the line boxes, we might think that there isn't
// visual overflow when there actually is visual overflow. This can become
@@ -348,16 +286,19 @@
// that affects the actual (but undetected) vertical overflow.
hasVisualOverflow = didOverflowWidth || didOverflowHeight
overflowShader = if (hasVisualOverflow && overflow == TextOverflow.Fade) {
- val fadeSizePainter = TextPainter(
+ val fadeSizeDelegate = TextDelegate(
text = AnnotatedString(text = "\u2026", textStyles = listOf()),
style = textStyle,
paragraphStyle = paragraphStyle,
density = density,
resourceLoader = resourceLoader
)
- fadeSizePainter.layoutText()
- val fadeWidth = fadeSizePainter.multiParagraph!!.width
- val fadeHeight = fadeSizePainter.multiParagraph!!.height
+ val paragraphForFadeSizeDelegate = fadeSizeDelegate.layoutText(
+ minWidth = 1.0f,
+ maxWidth = Float.POSITIVE_INFINITY
+ )
+ val fadeWidth = paragraphForFadeSizeDelegate.width
+ val fadeHeight = paragraphForFadeSizeDelegate.height
if (didOverflowWidth) {
val (fadeStart, fadeEnd) = if (textDirection == TextDirection.Rtl) {
Pair(fadeWidth, 0.0f)
@@ -381,6 +322,8 @@
} else {
null
}
+
+ layoutResult = LayoutResult(multiParagraph, size)
}
/**
@@ -394,13 +337,9 @@
* background, the text will not be visible by default.
*
* To set the text style, specify a [TextStyle] when creating the [TextSpan] that you pass to
- * the [TextPainter] constructor or to the [text] property.
+ * the [TextDelegate] constructor or to the [text] property.
*/
- fun paint(canvas: Canvas) {
- assert(!needsLayout) {
- "TextPainter.paint called when text geometry was not yet calculated.\n" +
- "Please call layout() before paint() to position the text before painting it."
- }
+ fun paint(canvas: Canvas) = assumeLayout { layoutResult ->
// Ideally we could compute the min/max intrinsic width/height with a
// non-destructive operation. However, currently, computing these values
// will destroy state inside the painter. If that happens, we need to
@@ -416,7 +355,7 @@
// layoutTextWithConstraints(constraints!!)
if (hasVisualOverflow) {
- val bounds = Rect.fromLTWH(0f, 0f, size.width, size.height)
+ val bounds = Rect.fromLTWH(0f, 0f, layoutResult.size.width, layoutResult.size.height)
if (overflowShader != null) {
// This layer limits what the shader below blends with to be just the text
// (as opposed to the text and its background).
@@ -427,7 +366,8 @@
canvas.clipRect(bounds)
}
- multiParagraph!!.paint(canvas)
+ layoutResult.multiParagraph.paint(canvas)
+ val size = layoutResult.size
if (hasVisualOverflow) {
if (overflowShader != null) {
val bounds = Rect.fromLTWH(0f, 0f, size.width, size.height)
@@ -450,10 +390,14 @@
* @param color a color to be used for drawing background.
* @param canvas the target canvas.
*/
- fun paintBackground(start: Int, end: Int, color: Color, canvas: Canvas) {
- assert(!needsLayout)
+ fun paintBackground(
+ start: Int,
+ end: Int,
+ color: Color,
+ canvas: Canvas
+ ) = assumeLayout { layoutResult ->
if (start == end) return
- val selectionPath = multiParagraph!!.getPathForRange(start, end)
+ val selectionPath = layoutResult.multiParagraph.getPathForRange(start, end)
// TODO(haoyuchang): check if move this paint to parameter is better
canvas.drawPath(selectionPath, Paint().apply { this.color = color })
}
@@ -466,51 +410,58 @@
* @param offset the cursor offset in the text.
* @param canvas the target canvas.
*/
- fun paintCursor(offset: Int, canvas: Canvas) {
- assert(!needsLayout)
- val cursorRect = multiParagraph!!.getCursorRect(offset)
+ fun paintCursor(offset: Int, canvas: Canvas) = assumeLayout { layoutResult ->
+ val cursorRect = layoutResult.multiParagraph.getCursorRect(offset)
canvas.drawRect(cursorRect, Paint().apply { this.color = Color.Black })
}
/**
* Returns the bottom y coordinate of the given line.
- *
- * @hide
*/
- @RestrictTo(RestrictTo.Scope.LIBRARY)
- fun getLineBottom(lineIndex: Int): Float {
- assert(!needsLayout)
- return multiParagraph!!.getLineBottom(lineIndex)
+ fun getLineBottom(lineIndex: Int): Float = assumeLayout { layoutResult ->
+ layoutResult.multiParagraph.getLineBottom(lineIndex)
}
/**
* Returns the line number on which the specified text offset appears.
* If you ask for a position before 0, you get 0; if you ask for a position
* beyond the end of the text, you get the last line.
- *
- * @hide
*/
- @RestrictTo(RestrictTo.Scope.LIBRARY)
- fun getLineForOffset(offset: Int): Int {
- assert(!needsLayout)
- return multiParagraph!!.getLineForOffset(offset)
+ fun getLineForOffset(offset: Int): Int = assumeLayout { layoutResult ->
+ layoutResult.multiParagraph.getLineForOffset(offset)
}
/**
* Get the primary horizontal position for the specified text offset.
- *
- * @hide
*/
- @RestrictTo(RestrictTo.Scope.LIBRARY)
- fun getPrimaryHorizontal(offset: Int): Float {
- assert(!needsLayout)
- return multiParagraph!!.getPrimaryHorizontal(offset)
+ fun getPrimaryHorizontal(offset: Int): Float = assumeLayout { layoutResult ->
+ layoutResult.multiParagraph.getPrimaryHorizontal(offset)
+ }
+
+ /**
+ * Get the secondary horizontal position for the specified text offset.
+ */
+ fun getSecondaryHorizontal(offset: Int): Float = assumeLayout { layoutResult ->
+ layoutResult.multiParagraph.getSecondaryHorizontal(offset)
+ }
+
+ /**
+ * Get the text direction of the paragraph containing the given offset.
+ */
+ fun getParagraphDirection(offset: Int): TextDirection = assumeLayout { layoutResult ->
+ layoutResult.multiParagraph.getParagraphDirection(offset)
+ }
+
+ /**
+ * Get the text direction of the character at the given offset.
+ */
+ fun getBidiRunDirection(offset: Int): TextDirection = assumeLayout { layoutResult ->
+ layoutResult.multiParagraph.getBidiRunDirection(offset)
}
/** Returns the character offset closest to the given graphical position. */
- fun getOffsetForPosition(position: PxPosition): Int {
- assert(!needsLayout)
- return multiParagraph!!.getOffsetForPosition(position)
+ fun getOffsetForPosition(position: PxPosition): Int = assumeLayout { layoutResult ->
+ layoutResult.multiParagraph.getOffsetForPosition(position)
}
/**
@@ -518,13 +469,9 @@
* the top, bottom, left and right of a character.
*
* Valid only after [layout] has been called.
- *
- * @hide
*/
- @RestrictTo(RestrictTo.Scope.LIBRARY)
- fun getBoundingBox(offset: Int): Rect {
- assert(!needsLayout)
- return multiParagraph!!.getBoundingBox(offset)
+ fun getBoundingBox(offset: Int): Rect = assumeLayout { layoutResult ->
+ layoutResult.multiParagraph.getBoundingBox(offset)
}
/**
@@ -535,8 +482,7 @@
* Word boundaries are defined more precisely in Unicode Standard Annex #29
* <http://www.unicode.org/reports/tr29/#Word_Boundaries>.
*/
- fun getWordBoundary(offset: Int): TextRange {
- assert(!needsLayout)
- return multiParagraph!!.getWordBoundary(offset)
+ fun getWordBoundary(offset: Int): TextRange = assumeLayout { layoutResult ->
+ layoutResult.multiParagraph.getWordBoundary(offset)
}
}
diff --git a/ui/ui-text/src/main/java/androidx/ui/text/TextStyle.kt b/ui/ui-text/src/main/java/androidx/ui/text/TextStyle.kt
index b500187..f8be302 100644
--- a/ui/ui-text/src/main/java/androidx/ui/text/TextStyle.kt
+++ b/ui/ui-text/src/main/java/androidx/ui/text/TextStyle.kt
@@ -30,6 +30,7 @@
import androidx.ui.graphics.lerp
import androidx.ui.lerp
import androidx.ui.painting.Shadow
+import java.util.Locale
/**
* Configuration object to define the text style.
diff --git a/ui/ui-text/src/main/java/androidx/ui/text/platform/AndroidParagraph.kt b/ui/ui-text/src/main/java/androidx/ui/text/platform/AndroidParagraph.kt
index ce4624b0..67a8a61 100644
--- a/ui/ui-text/src/main/java/androidx/ui/text/platform/AndroidParagraph.kt
+++ b/ui/ui-text/src/main/java/androidx/ui/text/platform/AndroidParagraph.kt
@@ -277,6 +277,18 @@
override fun getPrimaryHorizontal(offset: Int): Float =
ensureLayout.getPrimaryHorizontal(offset)
+ override fun getSecondaryHorizontal(offset: Int): Float =
+ ensureLayout.getSecondaryHorizontal(offset)
+
+ override fun getParagraphDirection(offset: Int): TextDirection {
+ val lineIndex = ensureLayout.getLineForOffset(offset)
+ val direction = ensureLayout.getParagraphDirection(lineIndex)
+ return if (direction == 1) TextDirection.Ltr else TextDirection.Rtl
+ }
+
+ override fun getBidiRunDirection(offset: Int): TextDirection {
+ return if (ensureLayout.isRtlCharAt(offset)) TextDirection.Rtl else TextDirection.Ltr
+ }
/**
* @return true if the given line is ellipsized, else false.
*/
@@ -284,9 +296,15 @@
ensureLayout.isEllipsisApplied(lineIndex)
override fun paint(canvas: Canvas) {
- val tmpLayout = layout ?: throw IllegalStateException("paint cannot be " +
- "called before layout() is called")
- tmpLayout.paint(canvas.nativeCanvas)
+ val nativeCanvas = canvas.nativeCanvas
+ if (didExceedMaxLines) {
+ nativeCanvas.save()
+ nativeCanvas.clipRect(0f, 0f, width, height)
+ }
+ ensureLayout.paint(nativeCanvas)
+ if (didExceedMaxLines) {
+ nativeCanvas.restore()
+ }
}
private fun createTypeface(style: TextStyle): Typeface {
@@ -437,7 +455,7 @@
style.locale?.let {
spannableString.setSpan(
// TODO(Migration/haoyuchang): support locale fallback in the framework
- LocaleSpan(Locale(it.languageCode, it.countryCode ?: "")),
+ LocaleSpan(Locale(it.language, it.country ?: "")),
start,
end,
Spanned.SPAN_EXCLUSIVE_EXCLUSIVE
@@ -497,8 +515,8 @@
locale?.let {
textPaint.textLocale = Locale(
- it.languageCode,
- it.countryCode ?: ""
+ it.language,
+ it.country ?: ""
)
}
diff --git a/ui/ui-framework/src/test/java/androidx/ui/core/PasswordVisualTransformationTest.kt b/ui/ui-text/src/test/java/androidx/ui/input/PasswordVisualTransformationTest.kt
similarity index 98%
rename from ui/ui-framework/src/test/java/androidx/ui/core/PasswordVisualTransformationTest.kt
rename to ui/ui-text/src/test/java/androidx/ui/input/PasswordVisualTransformationTest.kt
index 1284040..0b95106 100644
--- a/ui/ui-framework/src/test/java/androidx/ui/core/PasswordVisualTransformationTest.kt
+++ b/ui/ui-text/src/test/java/androidx/ui/input/PasswordVisualTransformationTest.kt
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package androidx.ui.core
+package androidx.ui.input
import androidx.ui.text.AnnotatedString
import org.junit.Assert.assertEquals
diff --git a/ui/ui-text/src/test/java/androidx/ui/text/TextDelegateTest.kt b/ui/ui-text/src/test/java/androidx/ui/text/TextDelegateTest.kt
new file mode 100644
index 0000000..d2c4df3
--- /dev/null
+++ b/ui/ui-text/src/test/java/androidx/ui/text/TextDelegateTest.kt
@@ -0,0 +1,180 @@
+/*
+ * 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.text
+
+import androidx.ui.core.Density
+import androidx.ui.painting.Canvas
+import androidx.ui.text.font.Font
+import androidx.ui.text.style.TextDirection
+import androidx.ui.text.style.TextOverflow
+import com.google.common.truth.Truth.assertThat
+import com.nhaarman.mockitokotlin2.mock
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+import java.util.Locale
+
+@RunWith(JUnit4::class)
+class TextDelegateTest() {
+ private val density = Density(density = 1f)
+ private val resourceLoader = mock<Font.ResourceLoader>()
+
+ @Test
+ fun `constructor with default values`() {
+ val textDelegate = TextDelegate(
+ text = AnnotatedString(text = ""),
+ density = density,
+ resourceLoader = resourceLoader)
+
+ assertThat(textDelegate.textDirection).isEqualTo(TextDirection.Ltr)
+ assertThat(textDelegate.maxLines).isNull()
+ assertThat(textDelegate.overflow).isEqualTo(TextOverflow.Clip)
+ assertThat(textDelegate.locale).isNull()
+ }
+
+ @Test
+ fun `constructor with customized text(TextSpan)`() {
+ val text = AnnotatedString("Hello")
+ val textDelegate = TextDelegate(
+ text = text,
+ density = density,
+ resourceLoader = resourceLoader
+ )
+
+ assertThat(textDelegate.text).isEqualTo(text)
+ }
+
+ @Test
+ fun `constructor with customized textDirection`() {
+ val textDelegate = TextDelegate(
+ text = AnnotatedString(text = ""),
+ paragraphStyle = ParagraphStyle(textDirection = TextDirection.Rtl),
+ density = density,
+ resourceLoader = resourceLoader
+ )
+
+ assertThat(textDelegate.textDirection).isEqualTo(TextDirection.Rtl)
+ }
+
+ @Test
+ fun `constructor with customized maxLines`() {
+ val maxLines = 8
+
+ val textDelegate = TextDelegate(
+ text = AnnotatedString(text = ""),
+ maxLines = maxLines,
+ density = density,
+ resourceLoader = resourceLoader
+ )
+
+ assertThat(textDelegate.maxLines).isEqualTo(maxLines)
+ }
+
+ @Test
+ fun `constructor with customized overflow`() {
+ val overflow = TextOverflow.Ellipsis
+
+ val textDelegate = TextDelegate(
+ text = AnnotatedString(text = ""),
+ overflow = overflow,
+ density = density,
+ resourceLoader = resourceLoader
+ )
+
+ assertThat(textDelegate.overflow).isEqualTo(overflow)
+ }
+
+ @Test
+ fun `constructor with customized locale`() {
+ val locale = Locale("en", "US")
+
+ val textDelegate = TextDelegate(
+ text = AnnotatedString(text = ""),
+ locale = locale,
+ density = density,
+ resourceLoader = resourceLoader
+ )
+
+ assertThat(textDelegate.locale).isEqualTo(locale)
+ }
+
+ @Test
+ fun `applyFloatingPointHack with value is integer toDouble`() {
+ assertThat(applyFloatingPointHack(2f)).isEqualTo(2.0f)
+ }
+
+ @Test
+ fun `applyFloatingPointHack with value smaller than half`() {
+ assertThat(applyFloatingPointHack(2.2f)).isEqualTo(3.0f)
+ }
+
+ @Test
+ fun `applyFloatingPointHack with value larger than half`() {
+ assertThat(applyFloatingPointHack(2.8f)).isEqualTo(3.0f)
+ }
+
+ @Test(expected = AssertionError::class)
+ fun `minIntrinsicWidth without layout assertion should fail`() {
+ val textDelegate = TextDelegate(
+ text = AnnotatedString(text = ""),
+ density = density,
+ resourceLoader = resourceLoader)
+
+ textDelegate.minIntrinsicWidth
+ }
+
+ @Test(expected = AssertionError::class)
+ fun `maxIntrinsicWidth without layout assertion should fail`() {
+ val textDelegate = TextDelegate(
+ text = AnnotatedString(text = ""),
+ density = density,
+ resourceLoader = resourceLoader)
+
+ textDelegate.maxIntrinsicWidth
+ }
+
+ @Test(expected = AssertionError::class)
+ fun `width without layout assertion should fail`() {
+ val textDelegate = TextDelegate(
+ text = AnnotatedString(text = ""),
+ density = density,
+ resourceLoader = resourceLoader)
+
+ textDelegate.width
+ }
+
+ @Test(expected = AssertionError::class)
+ fun `height without layout assertion should fail`() {
+ val textDelegate = TextDelegate(
+ text = AnnotatedString(text = ""),
+ density = density,
+ resourceLoader = resourceLoader)
+
+ textDelegate.height
+ }
+
+ @Test(expected = AssertionError::class)
+ fun `paint without layout assertion should fail`() {
+ val textDelegate = TextDelegate(
+ text = AnnotatedString(text = ""),
+ density = density,
+ resourceLoader = resourceLoader)
+ val canvas = mock<Canvas>()
+
+ textDelegate.paint(canvas)
+ }
+}
diff --git a/ui/ui-text/src/test/java/androidx/ui/text/TextPainterTest.kt b/ui/ui-text/src/test/java/androidx/ui/text/TextPainterTest.kt
deleted file mode 100644
index 5dcae69..0000000
--- a/ui/ui-text/src/test/java/androidx/ui/text/TextPainterTest.kt
+++ /dev/null
@@ -1,227 +0,0 @@
-/*
- * 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.text
-
-import androidx.ui.core.Constraints
-import androidx.ui.core.Density
-import androidx.ui.painting.Canvas
-import androidx.ui.text.font.Font
-import androidx.ui.text.style.TextAlign
-import androidx.ui.text.style.TextDirection
-import androidx.ui.text.style.TextOverflow
-import com.google.common.truth.Truth.assertThat
-import com.nhaarman.mockitokotlin2.mock
-import org.junit.Test
-import org.junit.runner.RunWith
-import org.junit.runners.JUnit4
-
-@RunWith(JUnit4::class)
-class TextPainterTest() {
- private val density = Density(density = 1f)
- private val resourceLoader = mock<Font.ResourceLoader>()
-
- @Test
- fun `constructor with default values`() {
- val textPainter = TextPainter(density = density, resourceLoader = resourceLoader)
-
- assertThat(textPainter.text).isNull()
- assertThat(textPainter.textAlign).isEqualTo(TextAlign.Start)
- assertThat(textPainter.textDirection).isEqualTo(TextDirection.Ltr)
- assertThat(textPainter.maxLines).isNull()
- assertThat(textPainter.overflow).isEqualTo(TextOverflow.Clip)
- assertThat(textPainter.locale).isNull()
- }
-
- @Test
- fun `constructor with customized text(TextSpan)`() {
- val text = AnnotatedString("Hello")
- val textPainter = TextPainter(
- text = text,
- density = density,
- resourceLoader = resourceLoader
- )
-
- assertThat(textPainter.text).isEqualTo(text)
- }
-
- @Test
- fun `constructor with customized textAlign`() {
- val textPainter = TextPainter(
- paragraphStyle = ParagraphStyle(textAlign = TextAlign.Left),
- density = density,
- resourceLoader = resourceLoader
- )
-
- assertThat(textPainter.textAlign).isEqualTo(TextAlign.Left)
- }
-
- @Test
- fun `constructor with customized textDirection`() {
- val textPainter = TextPainter(
- paragraphStyle = ParagraphStyle(textDirection = TextDirection.Rtl),
- density = density,
- resourceLoader = resourceLoader
- )
-
- assertThat(textPainter.textDirection).isEqualTo(TextDirection.Rtl)
- }
-
- @Test
- fun `constructor with customized maxLines`() {
- val maxLines = 8
-
- val textPainter = TextPainter(
- maxLines = maxLines,
- density = density,
- resourceLoader = resourceLoader
- )
-
- assertThat(textPainter.maxLines).isEqualTo(maxLines)
- }
-
- @Test
- fun `constructor with customized overflow`() {
- val overflow = TextOverflow.Ellipsis
-
- val textPainter = TextPainter(
- overflow = overflow,
- density = density,
- resourceLoader = resourceLoader
- )
-
- assertThat(textPainter.overflow).isEqualTo(overflow)
- }
-
- @Test
- fun `constructor with customized locale`() {
- val locale = Locale("en", "US")
-
- val textPainter = TextPainter(
- locale = locale,
- density = density,
- resourceLoader = resourceLoader
- )
-
- assertThat(textPainter.locale).isEqualTo(locale)
- }
-
- @Test
- fun `text setter`() {
- val textPainter = TextPainter(density = density, resourceLoader = resourceLoader)
- val text = AnnotatedString(text = "Hello")
-
- textPainter.text = text
-
- assertThat(textPainter.text).isEqualTo(text)
- assertThat(textPainter.multiParagraph).isNull()
- assertThat(textPainter.needsLayout).isTrue()
- }
-
- @Test
- fun `createParagraphStyle without TextStyle in AnnotatedText`() {
- val maxLines = 5
- val overflow = TextOverflow.Ellipsis
- val locale = Locale("en", "US")
- val text = AnnotatedString(text = "Hello")
- val textPainter = TextPainter(
- text = text,
- paragraphStyle = ParagraphStyle(
- textAlign = TextAlign.Center,
- textDirection = TextDirection.Rtl
- ),
- maxLines = maxLines,
- overflow = overflow,
- locale = locale,
- density = density,
- resourceLoader = resourceLoader
- )
-
- val paragraphStyle = textPainter.createParagraphStyle()
-
- assertThat(paragraphStyle.textAlign).isEqualTo(TextAlign.Center)
- assertThat(paragraphStyle.textDirection).isEqualTo(TextDirection.Rtl)
- }
-
- @Test
- fun `applyFloatingPointHack with value is integer toDouble`() {
- assertThat(applyFloatingPointHack(2f)).isEqualTo(2.0f)
- }
-
- @Test
- fun `applyFloatingPointHack with value smaller than half`() {
- assertThat(applyFloatingPointHack(2.2f)).isEqualTo(3.0f)
- }
-
- @Test
- fun `applyFloatingPointHack with value larger than half`() {
- assertThat(applyFloatingPointHack(2.8f)).isEqualTo(3.0f)
- }
-
- @Test(expected = AssertionError::class)
- fun `minIntrinsicWidth without layout assertion should fail`() {
- val textPainter = TextPainter(density = density, resourceLoader = resourceLoader)
-
- textPainter.minIntrinsicWidth
- }
-
- @Test(expected = AssertionError::class)
- fun `maxIntrinsicWidth without layout assertion should fail`() {
- val textPainter = TextPainter(density = density, resourceLoader = resourceLoader)
-
- textPainter.maxIntrinsicWidth
- }
-
- @Test(expected = AssertionError::class)
- fun `width without layout assertion should fail`() {
- val textPainter = TextPainter(density = density, resourceLoader = resourceLoader)
-
- textPainter.width
- }
-
- @Test(expected = AssertionError::class)
- fun `height without layout assertion should fail`() {
- val textPainter = TextPainter(density = density, resourceLoader = resourceLoader)
-
- textPainter.height
- }
-
- @Test(expected = AssertionError::class)
- fun `size without layout assertion should fail`() {
- val textPainter = TextPainter(density = density, resourceLoader = resourceLoader)
-
- textPainter.size
- }
-
- @Test(expected = AssertionError::class)
- fun `layout without text assertion should fail`() {
- val textPainter = TextPainter(
- paragraphStyle = ParagraphStyle(textDirection = TextDirection.Ltr),
- density = density,
- resourceLoader = resourceLoader
- )
-
- textPainter.layout(Constraints())
- }
-
- @Test(expected = AssertionError::class)
- fun `paint without layout assertion should fail`() {
- val textPainter = TextPainter(density = density, resourceLoader = resourceLoader)
- val canvas = mock<Canvas>()
-
- textPainter.paint(canvas)
- }
-}
diff --git a/ui/ui-text/src/test/java/androidx/ui/text/TextStyleTest.kt b/ui/ui-text/src/test/java/androidx/ui/text/TextStyleTest.kt
index 70ae35a..42c143e 100644
--- a/ui/ui-text/src/test/java/androidx/ui/text/TextStyleTest.kt
+++ b/ui/ui-text/src/test/java/androidx/ui/text/TextStyleTest.kt
@@ -34,6 +34,7 @@
import org.junit.Test
import org.junit.runner.RunWith
import org.junit.runners.JUnit4
+import java.util.Locale
@RunWith(JUnit4::class)
class TextStyleTest {
diff --git a/viewpager/build.gradle b/viewpager/build.gradle
index 2559de8..f1f7e65 100644
--- a/viewpager/build.gradle
+++ b/viewpager/build.gradle
@@ -17,7 +17,7 @@
dependencies {
api("androidx.annotation:annotation:1.1.0")
- implementation("androidx.core:core:1.1.0-rc01")
+ implementation("androidx.core:core:1.1.0")
api(project(":customview"))
androidTestImplementation(ANDROIDX_TEST_EXT_JUNIT)
diff --git a/viewpager2/build.gradle b/viewpager2/build.gradle
index 9c933df..6cda3c8 100644
--- a/viewpager2/build.gradle
+++ b/viewpager2/build.gradle
@@ -27,9 +27,9 @@
dependencies {
api("androidx.annotation:annotation:1.1.0")
- implementation("androidx.core:core:1.1.0-rc01")
+ implementation("androidx.core:core:1.1.0")
api("androidx.fragment:fragment:1.1.0-rc01")
- api("androidx.recyclerview:recyclerview:1.1.0-beta01")
+ api(project(":recyclerview:recyclerview"))
implementation("androidx.collection:collection:1.1.0")
androidTestImplementation(ANDROIDX_TEST_EXT_JUNIT)
diff --git a/viewpager2/src/androidTest/java/androidx/viewpager2/widget/BaseTest.kt b/viewpager2/src/androidTest/java/androidx/viewpager2/widget/BaseTest.kt
index 8185e8b..00719bf 100644
--- a/viewpager2/src/androidTest/java/androidx/viewpager2/widget/BaseTest.kt
+++ b/viewpager2/src/androidTest/java/androidx/viewpager2/widget/BaseTest.kt
@@ -39,6 +39,7 @@
import androidx.test.espresso.action.ViewActions.actionWithAssertions
import androidx.test.espresso.assertion.ViewAssertions.matches
import androidx.test.espresso.matcher.ViewMatchers.isAssignableFrom
+import androidx.test.espresso.matcher.ViewMatchers.isCompletelyDisplayed
import androidx.test.espresso.matcher.ViewMatchers.isDisplayed
import androidx.test.espresso.matcher.ViewMatchers.withId
import androidx.test.espresso.matcher.ViewMatchers.withText
@@ -492,7 +493,7 @@
)
assertThat("viewPager should be IDLE", viewPager.scrollState, equalTo(SCROLL_STATE_IDLE))
if (value != null) {
- onView(allOf<View>(withId(R.id.text_view), isDisplayed())).check(
+ onView(allOf<View>(withId(R.id.text_view), isCompletelyDisplayed())).check(
matches(withText(value))
)
}
diff --git a/viewpager2/src/androidTest/java/androidx/viewpager2/widget/PaddingMarginDecorationTest.kt b/viewpager2/src/androidTest/java/androidx/viewpager2/widget/PaddingMarginDecorationTest.kt
new file mode 100644
index 0000000..473935e
--- /dev/null
+++ b/viewpager2/src/androidTest/java/androidx/viewpager2/widget/PaddingMarginDecorationTest.kt
@@ -0,0 +1,429 @@
+/*
+ * 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.viewpager2.widget
+
+import android.graphics.Rect
+import android.view.View
+import android.view.ViewGroup
+import android.view.ViewGroup.MarginLayoutParams
+import androidx.recyclerview.widget.RecyclerView
+import androidx.test.filters.LargeTest
+import androidx.testutils.LocaleTestUtils
+import androidx.viewpager2.widget.PaddingMarginDecorationTest.Event.OnPageScrollStateChangedEvent
+import androidx.viewpager2.widget.PaddingMarginDecorationTest.Event.OnPageScrolledEvent
+import androidx.viewpager2.widget.PaddingMarginDecorationTest.Event.OnPageSelectedEvent
+import androidx.viewpager2.widget.PaddingMarginDecorationTest.TestConfig
+import androidx.viewpager2.widget.ViewPager2.ORIENTATION_HORIZONTAL
+import androidx.viewpager2.widget.ViewPager2.ORIENTATION_VERTICAL
+import androidx.viewpager2.widget.ViewPager2.SCROLL_STATE_DRAGGING
+import androidx.viewpager2.widget.ViewPager2.SCROLL_STATE_IDLE
+import androidx.viewpager2.widget.ViewPager2.SCROLL_STATE_SETTLING
+import androidx.viewpager2.widget.swipe.ViewAdapter
+import org.hamcrest.CoreMatchers.equalTo
+import org.junit.Assert.assertThat
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.Parameterized
+import java.util.concurrent.TimeUnit.SECONDS
+import kotlin.math.roundToInt
+
+@RunWith(Parameterized::class)
+@LargeTest
+class PaddingMarginDecorationTest(private val config: TestConfig) : BaseTest() {
+ data class TestConfig(
+ @ViewPager2.Orientation val orientation: Int,
+ val rtl: Boolean,
+ val vpPaddingPx: Int,
+ val rvPaddingPx: Int,
+ val itemMarginPx: Int,
+ val itemDecorationPx: Int
+ )
+
+ companion object {
+ @JvmStatic
+ @Parameterized.Parameters(name = "{0}")
+ fun spec(): List<TestConfig> = createTestSet()
+
+ // Set unequal decorations, to prevent symmetry from hiding bugs
+ // Similarly, make sure no margin is an exact multiple of another margin
+ // TODO(139452422): Set to 2/3/7/5 when PagerSnapHelper is fixed
+ const val fLeft = 2
+ const val fTop = 2
+ const val fRight = 2
+ const val fBottom = 2
+
+ fun View.applyMargin(margin: Int) {
+ val lp = layoutParams as MarginLayoutParams
+ lp.setMargins(margin * fLeft, margin * fTop, margin * fRight, margin * fBottom)
+ layoutParams = lp
+ }
+
+ fun View.applyPadding(padding: Int) {
+ setPadding(padding * fLeft, padding * fTop, padding * fRight, padding * fBottom)
+ }
+ }
+
+ private lateinit var test: Context
+ private val viewPager get() = test.viewPager
+
+ private val vpSize: Int get() {
+ return if (viewPager.isHorizontal) viewPager.width else viewPager.height
+ }
+
+ private val vpPadding: Int get() {
+ return if (viewPager.isHorizontal)
+ viewPager.paddingLeft + viewPager.paddingRight
+ else
+ viewPager.paddingTop + viewPager.paddingBottom
+ }
+
+ private val rvSize: Int get() {
+ val rv = viewPager.recyclerView
+ return if (viewPager.isHorizontal) rv.width else rv.height
+ }
+
+ private val rvMargin: Int get() {
+ return if (viewPager.isHorizontal)
+ horizontalMargin(viewPager.recyclerView.layoutParams)
+ else
+ verticalMargin(viewPager.recyclerView.layoutParams)
+ }
+
+ private val rvPadding: Int get() {
+ val rv = viewPager.recyclerView
+ return if (viewPager.isHorizontal)
+ rv.paddingLeft + rv.paddingRight
+ else
+ rv.paddingTop + rv.paddingBottom
+ }
+
+ private val itemSize: Int get() {
+ val item = viewPager.linearLayoutManager.findViewByPosition(0)!!
+ return if (viewPager.isHorizontal) item.width else item.height
+ }
+
+ private val itemMargin: Int get() {
+ val item = viewPager.linearLayoutManager.findViewByPosition(0)!!
+ return if (viewPager.isHorizontal)
+ horizontalMargin(item.layoutParams)
+ else
+ verticalMargin(item.layoutParams)
+ }
+
+ private val itemDecoration: Int get() {
+ val llm = viewPager.linearLayoutManager
+ val item = llm.findViewByPosition(0)!!
+ return if (viewPager.isHorizontal)
+ llm.getLeftDecorationWidth(item) + llm.getRightDecorationWidth(item)
+ else
+ llm.getTopDecorationHeight(item) + llm.getBottomDecorationHeight(item)
+ }
+
+ private val adapterProvider: AdapterProviderForItems get() {
+ return if (config.itemMarginPx > 0) {
+ { items -> { MarginViewAdapter(config.itemMarginPx, items) } }
+ } else {
+ { items -> { ViewAdapter(items) } }
+ }
+ }
+
+ class MarginViewAdapter(private val margin: Int, items: List<String>) : ViewAdapter(items) {
+ override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
+ return super.onCreateViewHolder(parent, viewType).apply { itemView.applyMargin(margin) }
+ }
+ }
+
+ class ItemDecorator(private val size: Int) : RecyclerView.ItemDecoration() {
+ override fun getItemOffsets(
+ outRect: Rect,
+ view: View,
+ parent: RecyclerView,
+ state: RecyclerView.State
+ ) {
+ outRect.left = size * fLeft
+ outRect.top = size * fTop
+ outRect.right = size * fRight
+ outRect.bottom = size * fBottom
+ }
+ }
+
+ override fun setUp() {
+ super.setUp()
+ if (config.rtl) {
+ localeUtil.resetLocale()
+ localeUtil.setLocale(LocaleTestUtils.RTL_LANGUAGE)
+ }
+ test = setUpTest(config.orientation)
+ test.runOnUiThreadSync {
+ viewPager.clipToPadding = false
+ viewPager.applyPadding(config.vpPaddingPx)
+ viewPager.recyclerView.clipToPadding = false
+ viewPager.recyclerView.applyPadding(config.rvPaddingPx)
+ viewPager.addItemDecoration(ItemDecorator(config.itemDecorationPx))
+ }
+ }
+
+ private fun horizontalMargin(lp: ViewGroup.LayoutParams): Int {
+ return if (lp is MarginLayoutParams) lp.leftMargin + lp.rightMargin else 0
+ }
+
+ private fun verticalMargin(lp: ViewGroup.LayoutParams): Int {
+ return if (lp is MarginLayoutParams) lp.topMargin + lp.bottomMargin else 0
+ }
+
+ @Test
+ fun test_pageSize() {
+ test.setAdapterSync(adapterProvider(stringSequence(1)))
+
+ val f = if (viewPager.isHorizontal) fLeft + fRight else fTop + fBottom
+
+ assertThat(vpPadding, equalTo(config.vpPaddingPx * f))
+ assertThat(rvPadding, equalTo(config.rvPaddingPx * f))
+ assertThat(itemMargin, equalTo(config.itemMarginPx * f))
+ assertThat(itemDecoration, equalTo(config.itemDecorationPx * f))
+
+ assertThat(viewPager.pageSize, equalTo(rvSize - rvPadding))
+ assertThat(viewPager.pageSize, equalTo(vpSize - vpPadding - rvMargin - rvPadding))
+ assertThat(viewPager.pageSize, equalTo(itemSize + itemDecoration + itemMargin))
+ }
+
+ /*
+ Sample log to guide the test
+
+ 1 -> 2
+ onPageScrollStateChanged,1
+ onPageScrolled,1,0.019444,21
+ onPageScrolled,1,0.082407,88
+ onPageScrolled,1,0.173148,187
+ onPageScrollStateChanged,2
+ onPageSelected,2
+ onPageScrolled,1,0.343518,370
+ onPageScrolled,1,0.855556,924
+ onPageScrolled,1,0.984259,1063
+ onPageScrolled,2,0.000000,0
+ onPageScrollStateChanged,0
+
+ 2 -> 1
+ onPageScrollStateChanged,1
+ onPageScrolled,1,0.972222,1050
+ onPageScrolled,1,0.910185,983
+ onPageScrolled,1,0.835185,902
+ onPageScrolled,1,0.764815,826
+ onPageScrollStateChanged,2
+ onPageSelected,1
+ onPageScrolled,1,0.616667,666
+ onPageScrolled,1,0.136111,147
+ onPageScrolled,1,0.015741,17
+ onPageScrolled,1,0.000000,0
+ onPageScrollStateChanged,0
+ */
+ @Test
+ fun test_swipeBetweenPages() {
+ test.setAdapterSync(adapterProvider(stringSequence(2)))
+ listOf(1, 0).forEach { targetPage ->
+ // given
+ val initialPage = viewPager.currentItem
+ assertThat(Math.abs(initialPage - targetPage), equalTo(1))
+
+ val callback = viewPager.addNewRecordingCallback()
+ val latch = viewPager.addWaitForScrolledLatch(targetPage)
+
+ // when
+ test.swipe(initialPage, targetPage)
+ latch.await(2, SECONDS)
+
+ // then
+ test.assertBasicState(targetPage)
+
+ callback.apply {
+ // verify all events
+ assertThat(draggingIx, equalTo(0))
+ assertThat(settlingIx, isBetweenInEx(firstScrolledIx + 1, lastScrolledIx))
+ assertThat(idleIx, equalTo(lastIx))
+ assertThat(pageSelectedIx(targetPage), equalTo(settlingIx + 1))
+ assertThat(scrollEventCount, equalTo(eventCount - 4))
+
+ // dive into scroll events
+ val sortOrder =
+ if (targetPage - initialPage > 0) SortOrder.ASC
+ else SortOrder.DESC
+ scrollEvents.assertPositionSorted(sortOrder)
+ scrollEvents.assertOffsetSorted(sortOrder)
+ scrollEvents.assertValueSanity(initialPage, targetPage, viewPager.pageSize)
+ scrollEvents.assertLastCorrect(targetPage)
+ scrollEvents.assertMaxShownPages()
+ }
+
+ viewPager.unregisterOnPageChangeCallback(callback)
+ }
+ }
+
+ /*
+ Before page 0
+ onPageScrollStateChanged,1
+ onPageScrolled,0,0.000000,0
+ onPageScrolled,0,0.000000,0
+ onPageScrolled,0,0.000000,0
+ onPageScrolled,0,0.000000,0
+ onPageScrollStateChanged,0
+
+ After page 2
+ onPageScrollStateChanged,1
+ onPageScrolled,2,0.000000,0
+ onPageScrolled,2,0.000000,0
+ onPageScrolled,2,0.000000,0
+ onPageScrollStateChanged,0
+ */
+ @Test
+ fun test_swipeBeyondEdgePages() {
+ val totalPages = 2
+ val edgePages = setOf(0, totalPages - 1)
+
+ test.setAdapterSync(adapterProvider(stringSequence(totalPages)))
+ listOf(0, 1, 1).forEach { targetPage ->
+ // given
+ val initialPage = viewPager.currentItem
+ val callback = viewPager.addNewRecordingCallback()
+ val latch = viewPager.addWaitForScrolledLatch(targetPage)
+
+ // when
+ test.swipe(initialPage, targetPage)
+ latch.await(2, SECONDS)
+
+ // then
+ test.assertBasicState(targetPage)
+
+ if (targetPage == initialPage && edgePages.contains(targetPage)) {
+ callback.apply {
+ // verify all events
+ assertThat("Events should start with a state change to DRAGGING",
+ draggingIx, equalTo(0))
+ assertThat("Last event should be a state change to IDLE",
+ idleIx, equalTo(lastIx))
+ assertThat("All events but the state changes to DRAGGING and IDLE" +
+ " should be scroll events",
+ scrollEventCount, equalTo(eventCount - 2))
+
+ // dive into scroll events
+ scrollEvents.forEach {
+ assertThat("All scroll events should report page $targetPage",
+ it.position, equalTo(targetPage))
+ assertThat("All scroll events should report an offset of 0f",
+ it.positionOffset, equalTo(0f))
+ assertThat("All scroll events should report an offset of 0px",
+ it.positionOffsetPixels, equalTo(0))
+ }
+ }
+ }
+
+ viewPager.unregisterOnPageChangeCallback(callback)
+ }
+ }
+
+ private fun ViewPager2.addNewRecordingCallback(): RecordingCallback {
+ return RecordingCallback().also { registerOnPageChangeCallback(it) }
+ }
+
+ private sealed class Event {
+ data class OnPageScrolledEvent(
+ val position: Int,
+ val positionOffset: Float,
+ val positionOffsetPixels: Int
+ ) : Event()
+ data class OnPageSelectedEvent(val position: Int) : Event()
+ data class OnPageScrollStateChangedEvent(val state: Int) : Event()
+ }
+
+ private class RecordingCallback : ViewPager2.OnPageChangeCallback() {
+ private val events = mutableListOf<Event>()
+
+ val scrollEvents get() = events.mapNotNull { it as? OnPageScrolledEvent }
+ val eventCount get() = events.size
+ val scrollEventCount get() = scrollEvents.size
+ val lastIx get() = events.size - 1
+ val firstScrolledIx get() = events.indexOfFirst { it is OnPageScrolledEvent }
+ val lastScrolledIx get() = events.indexOfLast { it is OnPageScrolledEvent }
+ val settlingIx get() = events.indexOf(OnPageScrollStateChangedEvent(SCROLL_STATE_SETTLING))
+ val draggingIx get() = events.indexOf(OnPageScrollStateChangedEvent(SCROLL_STATE_DRAGGING))
+ val idleIx get() = events.indexOf(OnPageScrollStateChangedEvent(SCROLL_STATE_IDLE))
+ val pageSelectedIx: (page: Int) -> Int = { events.indexOf(OnPageSelectedEvent(it)) }
+
+ override fun onPageScrolled(
+ position: Int,
+ positionOffset: Float,
+ positionOffsetPixels: Int
+ ) {
+ events.add(OnPageScrolledEvent(position, positionOffset, positionOffsetPixels))
+ }
+
+ override fun onPageSelected(position: Int) {
+ events.add(OnPageSelectedEvent(position))
+ }
+
+ override fun onPageScrollStateChanged(state: Int) {
+ events.add(OnPageScrollStateChangedEvent(state))
+ }
+ }
+
+ private fun List<OnPageScrolledEvent>.assertPositionSorted(sortOrder: SortOrder) {
+ map { it.position }.assertSorted { it * sortOrder.sign }
+ }
+
+ private fun List<OnPageScrolledEvent>.assertLastCorrect(targetPage: Int) {
+ last().apply {
+ assertThat(position, equalTo(targetPage))
+ assertThat(positionOffsetPixels, equalTo(0))
+ }
+ }
+
+ private fun List<OnPageScrolledEvent>.assertValueSanity(
+ initialPage: Int,
+ otherPage: Int,
+ pageSize: Int
+ ) = forEach {
+ assertThat(it.position, isBetweenInInMinMax(initialPage, otherPage))
+ assertThat(it.positionOffset, isBetweenInEx(0f, 1f))
+ assertThat((it.positionOffset * pageSize).roundToInt(), equalTo(it.positionOffsetPixels))
+ }
+
+ private fun List<OnPageScrolledEvent>.assertOffsetSorted(sortOrder: SortOrder) {
+ map { it.position + it.positionOffset.toDouble() }.assertSorted { it * sortOrder.sign }
+ }
+
+ private fun List<OnPageScrolledEvent>.assertMaxShownPages() {
+ assertThat(map { it.position }.distinct().size, isBetweenInIn(0, 4))
+ }
+}
+
+// region Test Suite creation
+
+private fun createTestSet(): List<TestConfig> {
+ return listOf(ORIENTATION_HORIZONTAL, ORIENTATION_VERTICAL).flatMap { orientation ->
+ listOf(false, true).flatMap { rtl ->
+ listOf(
+ TestConfig(orientation, rtl, 0, 0, 0, 0),
+ TestConfig(orientation, rtl, 0, 0, 0, 10),
+ TestConfig(orientation, rtl, 0, 0, 10, 0),
+ TestConfig(orientation, rtl, 0, 10, 0, 0),
+ TestConfig(orientation, rtl, 10, 0, 0, 0),
+ TestConfig(orientation, rtl, 1, 2, 3, 4)
+ )
+ }
+ }
+}
+
+// endregion
diff --git a/viewpager2/src/androidTest/java/androidx/viewpager2/widget/PageChangeCallbackTest.kt b/viewpager2/src/androidTest/java/androidx/viewpager2/widget/PageChangeCallbackTest.kt
index da60cc2..cc4bb92 100644
--- a/viewpager2/src/androidTest/java/androidx/viewpager2/widget/PageChangeCallbackTest.kt
+++ b/viewpager2/src/androidTest/java/androidx/viewpager2/widget/PageChangeCallbackTest.kt
@@ -40,7 +40,6 @@
import androidx.viewpager2.widget.ViewPager2.SCROLL_STATE_IDLE
import androidx.viewpager2.widget.ViewPager2.SCROLL_STATE_SETTLING
import androidx.viewpager2.widget.swipe.PageSwiperManual
-import androidx.viewpager2.widget.swipe.ViewAdapter
import org.hamcrest.CoreMatchers.equalTo
import org.hamcrest.CoreMatchers.not
import org.hamcrest.Matchers.allOf
@@ -60,8 +59,7 @@
class PageChangeCallbackTest(private val config: TestConfig) : BaseTest() {
data class TestConfig(
@ViewPager2.Orientation val orientation: Int,
- val rtl: Boolean,
- val pageMarginPx: Int
+ val rtl: Boolean
)
companion object {
@@ -78,26 +76,6 @@
}
}
- private val adapterProvider: AdapterProviderForItems get() {
- return if (config.pageMarginPx > 0) {
- { items -> { MarginViewAdapter(config.pageMarginPx, items) } }
- } else {
- { items -> { ViewAdapter(items) } }
- }
- }
-
- class MarginViewAdapter(private val margin: Int, items: List<String>) : ViewAdapter(items) {
- override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
- val viewHolder = super.onCreateViewHolder(parent, viewType)
- val lp = viewHolder.itemView.layoutParams as ViewGroup.MarginLayoutParams
- // Set unequal margins, to prevent symmetry from hiding bugs
- // Similarly, make sure no margin is an exact multiple of another margin
- lp.setMargins(margin * 2, margin * 3, margin * 7, margin * 5)
- viewHolder.itemView.layoutParams = lp
- return viewHolder
- }
- }
-
/*
Sample log to guide the test
@@ -131,7 +109,7 @@
@Test
fun test_swipeBetweenPages() {
setUpTest(config.orientation).apply {
- setAdapterSync(adapterProvider(stringSequence(4)))
+ setAdapterSync(viewAdapterProvider(stringSequence(4)))
listOf(1, 2, 3, 2, 1, 0).forEach { targetPage ->
// given
val initialPage = viewPager.currentItem
@@ -194,7 +172,7 @@
setUpTest(config.orientation).apply {
- setAdapterSync(adapterProvider(stringSequence(totalPages)))
+ setAdapterSync(viewAdapterProvider(stringSequence(totalPages)))
listOf(0, 0, 1, 2, 2, 2, 1, 2, 2, 2, 1, 0, 0, 0).forEach { targetPage ->
// given
val initialPage = viewPager.currentItem
@@ -257,7 +235,7 @@
fun test_peekOnAdjacentPage_next() {
// given
setUpTest(config.orientation).apply {
- setAdapterSync(adapterProvider(stringSequence(3)))
+ setAdapterSync(viewAdapterProvider(stringSequence(3)))
val callback = viewPager.addNewRecordingCallback()
val latch = viewPager.addWaitForScrolledLatch(0)
@@ -316,7 +294,7 @@
fun test_peekOnAdjacentPage_previous() {
// given
setUpTest(config.orientation).apply {
- setAdapterSync(adapterProvider(stringSequence(3)))
+ setAdapterSync(viewAdapterProvider(stringSequence(3)))
viewPager.setCurrentItemSync(2, false, 1, SECONDS)
@@ -393,7 +371,7 @@
fun test_selectItemProgrammatically_smoothScroll() {
// given
setUpTest(config.orientation).apply {
- setAdapterSync(adapterProvider(stringSequence(1000)))
+ setAdapterSync(viewAdapterProvider(stringSequence(1000)))
// when
listOf(6, 5, 6, 3, 10, 0, 0, 999, 999, 0).forEach { targetPage ->
@@ -434,7 +412,7 @@
fun test_multiplePageChanges() {
// given
setUpTest(config.orientation).apply {
- setAdapterSync(adapterProvider(stringSequence(10)))
+ setAdapterSync(viewAdapterProvider(stringSequence(10)))
val targetPages = listOf(4, 9)
val callback = viewPager.addNewRecordingCallback()
val latch = viewPager.addWaitForScrolledLatch(targetPages.last(), true)
@@ -484,7 +462,7 @@
fun test_noSmoothScroll_after_smoothScroll() {
// given
setUpTest(config.orientation).apply {
- setAdapterSync(adapterProvider(stringSequence(6)))
+ setAdapterSync(viewAdapterProvider(stringSequence(6)))
val targetPage = 4
val marker = 1
val callback = viewPager.addNewRecordingCallback()
@@ -602,7 +580,7 @@
// given
assertThat(targetPage, greaterThanOrEqualTo(4))
setUpTest(config.orientation).apply {
- val adapterProvider = adapterProvider(stringSequence(5))
+ val adapterProvider = viewAdapterProvider(stringSequence(5))
setAdapterSync(adapterProvider)
val marker = 1
val callback = viewPager.addNewRecordingCallback()
@@ -662,7 +640,7 @@
fun test_selectItemProgrammatically_noSmoothScroll() {
// given
setUpTest(config.orientation).apply {
- setAdapterSync(adapterProvider(stringSequence(3)))
+ setAdapterSync(viewAdapterProvider(stringSequence(3)))
// when
listOf(2, 2, 0, 0, 1, 2, 1, 0).forEach { targetPage ->
@@ -694,7 +672,7 @@
fun test_swipeReleaseSwipeBack() {
// given
val test = setUpTest(config.orientation)
- test.setAdapterSync(adapterProvider(stringSequence(3)))
+ test.setAdapterSync(viewAdapterProvider(stringSequence(3)))
val currentPage = test.viewPager.currentItem
val halfPage = test.viewPager.pageSize / 2f
val pageSwiper = PageSwiperManual(test.viewPager)
@@ -768,7 +746,7 @@
private fun test_selectItemProgrammatically_noCallback(smoothScroll: Boolean) {
// given
setUpTest(config.orientation).apply {
- setAdapterSync(adapterProvider(stringSequence(3)))
+ setAdapterSync(viewAdapterProvider(stringSequence(3)))
// when
listOf(2, 2, 0, 0, 1, 2, 1, 0).forEach { targetPage ->
@@ -923,7 +901,7 @@
private fun test_setCurrentItem_outOfBounds(smoothScroll: Boolean) {
val test = setUpTest(config.orientation)
val n = 3
- test.setAdapterSync(adapterProvider(stringSequence(n)))
+ test.setAdapterSync(viewAdapterProvider(stringSequence(n)))
val adapterCount = test.viewPager.adapter!!.itemCount
listOf(-5, -1, n, n + 1, adapterCount, adapterCount + 1).forEach { targetPage ->
@@ -1199,10 +1177,8 @@
private fun createTestSet(): List<TestConfig> {
return listOf(ORIENTATION_HORIZONTAL, ORIENTATION_VERTICAL).flatMap { orientation ->
- listOf(true, false).flatMap { rtl ->
- listOf(0, 10, -10).map { margin ->
- TestConfig(orientation, rtl, margin)
- }
+ listOf(true, false).map { rtl ->
+ TestConfig(orientation, rtl)
}
}
}
diff --git a/viewpager2/src/main/java/androidx/viewpager2/widget/ScrollEventAdapter.java b/viewpager2/src/main/java/androidx/viewpager2/widget/ScrollEventAdapter.java
index 456786f..f122b43 100644
--- a/viewpager2/src/main/java/androidx/viewpager2/widget/ScrollEventAdapter.java
+++ b/viewpager2/src/main/java/androidx/viewpager2/widget/ScrollEventAdapter.java
@@ -16,8 +16,6 @@
package androidx.viewpager2.widget;
-import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
-
import static androidx.viewpager2.widget.ViewPager2.ORIENTATION_HORIZONTAL;
import static androidx.viewpager2.widget.ViewPager2.SCROLL_STATE_DRAGGING;
import static androidx.viewpager2.widget.ViewPager2.SCROLL_STATE_IDLE;
@@ -27,6 +25,7 @@
import static java.lang.annotation.RetentionPolicy.SOURCE;
import android.view.View;
+import android.view.ViewGroup.LayoutParams;
import android.view.ViewGroup.MarginLayoutParams;
import androidx.annotation.IntDef;
@@ -44,13 +43,6 @@
* relative to the pages and exposes this position via ({@link #getRelativeScrollPosition()}.
*/
final class ScrollEventAdapter extends RecyclerView.OnScrollListener {
- private static final MarginLayoutParams ZERO_MARGIN_LAYOUT_PARAMS;
-
- static {
- ZERO_MARGIN_LAYOUT_PARAMS = new MarginLayoutParams(MATCH_PARENT, MATCH_PARENT);
- ZERO_MARGIN_LAYOUT_PARAMS.setMargins(0, 0, 0, 0);
- }
-
/** @hide */
@Retention(SOURCE)
@IntDef({STATE_IDLE, STATE_IN_PROGRESS_MANUAL_DRAG, STATE_IN_PROGRESS_SMOOTH_SCROLL,
@@ -67,8 +59,9 @@
private static final int NO_POSITION = -1;
private OnPageChangeCallback mCallback;
- private final @NonNull LinearLayoutManager mLayoutManager;
private final @NonNull ViewPager2 mViewPager;
+ private final @NonNull RecyclerView mRecyclerView;
+ private final @NonNull LinearLayoutManager mLayoutManager;
// state related fields
private @AdapterState int mAdapterState;
@@ -82,8 +75,10 @@
private boolean mFakeDragging;
ScrollEventAdapter(@NonNull ViewPager2 viewPager) {
- mLayoutManager = viewPager.mLayoutManager;
mViewPager = viewPager;
+ mRecyclerView = mViewPager.mRecyclerView;
+ //noinspection ConstantConditions
+ mLayoutManager = (LinearLayoutManager) mRecyclerView.getLayoutManager();
mScrollValues = new ScrollEventValues();
resetState();
}
@@ -239,24 +234,34 @@
return;
}
- // TODO(123350297): automated test for this
- MarginLayoutParams margin =
- (firstVisibleView.getLayoutParams() instanceof MarginLayoutParams)
- ? (MarginLayoutParams) firstVisibleView.getLayoutParams()
- : ZERO_MARGIN_LAYOUT_PARAMS;
+ int leftDecorations = mLayoutManager.getLeftDecorationWidth(firstVisibleView);
+ int rightDecorations = mLayoutManager.getRightDecorationWidth(firstVisibleView);
+ int topDecorations = mLayoutManager.getTopDecorationHeight(firstVisibleView);
+ int bottomDecorations = mLayoutManager.getBottomDecorationHeight(firstVisibleView);
+
+ LayoutParams params = firstVisibleView.getLayoutParams();
+ if (params instanceof MarginLayoutParams) {
+ MarginLayoutParams margin = (MarginLayoutParams) params;
+ leftDecorations += margin.leftMargin;
+ rightDecorations += margin.rightMargin;
+ topDecorations += margin.topMargin;
+ bottomDecorations += margin.bottomMargin;
+ }
+
+ int decoratedHeight = firstVisibleView.getHeight() + topDecorations + bottomDecorations;
+ int decoratedWidth = firstVisibleView.getWidth() + leftDecorations + rightDecorations;
boolean isHorizontal = mLayoutManager.getOrientation() == ORIENTATION_HORIZONTAL;
int start, sizePx;
if (isHorizontal) {
- sizePx = firstVisibleView.getWidth() + margin.leftMargin + margin.rightMargin;
- if (!mViewPager.isRtl()) {
- start = firstVisibleView.getLeft() - margin.leftMargin;
- } else {
- start = sizePx - firstVisibleView.getRight() - margin.rightMargin;
+ sizePx = decoratedWidth;
+ start = firstVisibleView.getLeft() - leftDecorations - mRecyclerView.getPaddingLeft();
+ if (mViewPager.isRtl()) {
+ start = -start;
}
} else {
- sizePx = firstVisibleView.getHeight() + margin.topMargin + margin.bottomMargin;
- start = firstVisibleView.getTop() - margin.topMargin;
+ sizePx = decoratedHeight;
+ start = firstVisibleView.getTop() - topDecorations - mRecyclerView.getPaddingTop();
}
values.mOffsetPx = -start;
diff --git a/viewpager2/src/main/java/androidx/viewpager2/widget/ViewPager2.java b/viewpager2/src/main/java/androidx/viewpager2/widget/ViewPager2.java
index d0aee32..3bfaf3d 100644
--- a/viewpager2/src/main/java/androidx/viewpager2/widget/ViewPager2.java
+++ b/viewpager2/src/main/java/androidx/viewpager2/widget/ViewPager2.java
@@ -138,10 +138,10 @@
}
};
- LinearLayoutManager mLayoutManager;
+ private LinearLayoutManager mLayoutManager;
private int mPendingCurrentItem = NO_POSITION;
private Parcelable mPendingAdapterState;
- private RecyclerView mRecyclerView;
+ RecyclerView mRecyclerView;
private PagerSnapHelper mPagerSnapHelper;
ScrollEventAdapter mScrollEventAdapter;
private CompositeOnPageChangeCallback mPageChangeEventDispatcher;
@@ -540,9 +540,10 @@
}
int getPageSize() {
+ final RecyclerView rv = mRecyclerView;
return getOrientation() == ORIENTATION_HORIZONTAL
- ? getWidth() - getPaddingLeft() - getPaddingRight()
- : getHeight() - getPaddingTop() - getPaddingBottom();
+ ? rv.getWidth() - rv.getPaddingLeft() - rv.getPaddingRight()
+ : rv.getHeight() - rv.getPaddingTop() - rv.getPaddingBottom();
}
/**
diff --git a/webkit/api/1.1.0-alpha03.txt b/webkit/api/1.1.0-alpha03.txt
index 44938cf..2936afa 100644
--- a/webkit/api/1.1.0-alpha03.txt
+++ b/webkit/api/1.1.0-alpha03.txt
@@ -3,7 +3,7 @@
public final class ProxyConfig {
method public java.util.List<java.lang.String!> getBypassRules();
- method public java.util.List<androidx.core.util.Pair<java.lang.String!,java.lang.String!>!> getProxyRules();
+ method public java.util.List<androidx.webkit.ProxyConfig.ProxyRule!> getProxyRules();
field public static final String MATCH_ALL_SCHEMES = "*";
field public static final String MATCH_HTTP = "http";
field public static final String MATCH_HTTPS = "https";
@@ -22,6 +22,11 @@
method public androidx.webkit.ProxyConfig.Builder removeImplicitRules();
}
+ public static final class ProxyConfig.ProxyRule {
+ method public String getSchemeFilter();
+ method public String getUrl();
+ }
+
public abstract class ProxyController {
method public abstract void clearProxyOverride(java.util.concurrent.Executor, Runnable);
method @RequiresFeature(name=androidx.webkit.WebViewFeature.PROXY_OVERRIDE, enforcement="androidx.webkit.WebViewFeature#isFeatureSupported") public static androidx.webkit.ProxyController getInstance();
diff --git a/webkit/api/current.txt b/webkit/api/current.txt
index 44938cf..2936afa 100644
--- a/webkit/api/current.txt
+++ b/webkit/api/current.txt
@@ -3,7 +3,7 @@
public final class ProxyConfig {
method public java.util.List<java.lang.String!> getBypassRules();
- method public java.util.List<androidx.core.util.Pair<java.lang.String!,java.lang.String!>!> getProxyRules();
+ method public java.util.List<androidx.webkit.ProxyConfig.ProxyRule!> getProxyRules();
field public static final String MATCH_ALL_SCHEMES = "*";
field public static final String MATCH_HTTP = "http";
field public static final String MATCH_HTTPS = "https";
@@ -22,6 +22,11 @@
method public androidx.webkit.ProxyConfig.Builder removeImplicitRules();
}
+ public static final class ProxyConfig.ProxyRule {
+ method public String getSchemeFilter();
+ method public String getUrl();
+ }
+
public abstract class ProxyController {
method public abstract void clearProxyOverride(java.util.concurrent.Executor, Runnable);
method @RequiresFeature(name=androidx.webkit.WebViewFeature.PROXY_OVERRIDE, enforcement="androidx.webkit.WebViewFeature#isFeatureSupported") public static androidx.webkit.ProxyController getInstance();
diff --git a/webkit/build.gradle b/webkit/build.gradle
index 8c0c18d..622bf29 100644
--- a/webkit/build.gradle
+++ b/webkit/build.gradle
@@ -26,7 +26,7 @@
dependencies {
api("androidx.annotation:annotation:1.1.0")
- api("androidx.core:core:1.1.0-rc01")
+ api("androidx.core:core:1.1.0")
androidTestImplementation(OKHTTP_MOCKWEBSERVER)
androidTestImplementation(ANDROIDX_TEST_EXT_JUNIT)
diff --git a/webkit/integration-tests/testapp/src/main/java/com/example/androidx/webkit/ForceDarkActivity.java b/webkit/integration-tests/testapp/src/main/java/com/example/androidx/webkit/ForceDarkActivity.java
index aa8531b..37600b6 100644
--- a/webkit/integration-tests/testapp/src/main/java/com/example/androidx/webkit/ForceDarkActivity.java
+++ b/webkit/integration-tests/testapp/src/main/java/com/example/androidx/webkit/ForceDarkActivity.java
@@ -27,11 +27,6 @@
import androidx.webkit.WebSettingsCompat;
import androidx.webkit.WebViewFeature;
-// TODO(amalova): enable the following attributes once Q SDK is available.
-// <item name="android:forceDarkAllowed">true</item>
-// <item name="android:isLightTheme">true</item>
-// This attributes are necessary to make force dark auto mode works.
-
/**
* An {@link Activity} to exercise Force Dark functionality.
* It shows WebViews side by side with different dark mode settings.
diff --git a/webkit/src/androidTest/java/androidx/webkit/WebViewAssetLoaderTest.java b/webkit/src/androidTest/java/androidx/webkit/WebViewAssetLoaderTest.java
index f5a667b..d95ffda 100644
--- a/webkit/src/androidTest/java/androidx/webkit/WebViewAssetLoaderTest.java
+++ b/webkit/src/androidTest/java/androidx/webkit/WebViewAssetLoaderTest.java
@@ -67,12 +67,12 @@
}
@Override
- public InputStream openAsset(Uri uri) {
+ public InputStream openAsset(String path) {
return null;
}
@Override
- public InputStream openResource(Uri uri) {
+ public InputStream openResource(String path) {
return null;
}
}
@@ -171,8 +171,8 @@
PathHandler assetsPathHandler = new AssetsPathHandler(new MockAssetHelper() {
@Override
- public InputStream openAsset(Uri url) {
- if (url.getPath().equals("www/test.html")) {
+ public InputStream openAsset(String path) {
+ if (path.equals("www/test.html")) {
try {
return new ByteArrayInputStream(testHtmlContents.getBytes(ENCODING));
} catch (IOException e) {
@@ -198,8 +198,8 @@
PathHandler resourcesPathHandler = new ResourcesPathHandler(new MockAssetHelper() {
@Override
- public InputStream openResource(Uri uri) {
- if (uri.getPath().equals("raw/test.html")) {
+ public InputStream openResource(String path) {
+ if (path.equals("raw/test.html")) {
try {
return new ByteArrayInputStream(testHtmlContents.getBytes(ENCODING));
} catch (IOException e) {
@@ -402,7 +402,7 @@
AssetHelper mockAssetHelper = new MockAssetHelper() {
@Override
- public InputStream openResource(Uri uri) {
+ public InputStream openResource(String path) {
try {
return new ByteArrayInputStream(testHtmlContents.getBytes(ENCODING));
} catch (IOException e) {
diff --git a/webkit/src/androidTest/java/androidx/webkit/WebViewClientCompatTest.java b/webkit/src/androidTest/java/androidx/webkit/WebViewClientCompatTest.java
index d48607b..b8288c0 100644
--- a/webkit/src/androidTest/java/androidx/webkit/WebViewClientCompatTest.java
+++ b/webkit/src/androidTest/java/androidx/webkit/WebViewClientCompatTest.java
@@ -18,7 +18,6 @@
import android.graphics.Bitmap;
import android.net.Uri;
-import android.webkit.ValueCallback;
import android.webkit.WebResourceRequest;
import android.webkit.WebResourceResponse;
import android.webkit.WebView;
@@ -159,18 +158,9 @@
private void clickOnLinkUsingJs(final String linkId, WebViewOnUiThread webViewOnUiThread)
throws InterruptedException, ExecutionException, TimeoutException {
- final ResolvableFuture<String> javascriptFuture = ResolvableFuture.create();
- ValueCallback<String> callback = new ValueCallback<String>() {
- @Override
- public void onReceiveValue(String value) {
- javascriptFuture.set(value);
- }
- };
- webViewOnUiThread.evaluateJavascript(
+ webViewOnUiThread.evaluateJavascriptSync(
"document.getElementById('" + linkId + "').click();"
- + "console.log('element with id [" + linkId + "] clicked');", callback);
- // TODO(ntfschr): consider asserting the value.
- WebkitUtils.waitForFuture(javascriptFuture);
+ + "console.log('element with id [" + linkId + "] clicked');");
}
/**
diff --git a/webkit/src/androidTest/java/androidx/webkit/WebViewOnUiThread.java b/webkit/src/androidTest/java/androidx/webkit/WebViewOnUiThread.java
index 37053db..8790899 100644
--- a/webkit/src/androidTest/java/androidx/webkit/WebViewOnUiThread.java
+++ b/webkit/src/androidTest/java/androidx/webkit/WebViewOnUiThread.java
@@ -287,8 +287,17 @@
});
}
- void evaluateJavascript(final String script, final ValueCallback<String> result) {
- WebkitUtils.onMainThreadSync(() -> mWebView.evaluateJavascript(script, result));
+ /**
+ * Execute javascript synchronously, returning the result.
+ */
+ public String evaluateJavascriptSync(final String script) {
+ final ResolvableFuture<String> future = ResolvableFuture.create();
+ evaluateJavascript(script, result -> future.set(result));
+ return WebkitUtils.waitForFuture(future);
+ }
+
+ public void evaluateJavascript(final String script, final ValueCallback<String> result) {
+ WebkitUtils.onMainThread(() -> mWebView.evaluateJavascript(script, result));
}
public WebViewClient getWebViewClient() {
diff --git a/webkit/src/androidTest/java/androidx/webkit/internal/AssetHelperTest.java b/webkit/src/androidTest/java/androidx/webkit/internal/AssetHelperTest.java
index 5696e8a9..82aec1c 100644
--- a/webkit/src/androidTest/java/androidx/webkit/internal/AssetHelperTest.java
+++ b/webkit/src/androidTest/java/androidx/webkit/internal/AssetHelperTest.java
@@ -17,7 +17,6 @@
package androidx.webkit.internal;
import android.content.Context;
-import android.net.Uri;
import android.util.Log;
import androidx.test.InstrumentationRegistry;
@@ -61,7 +60,7 @@
@Test
@SmallTest
public void testOpenExistingResource() {
- InputStream stream = mAssetHelper.openResource(Uri.parse("raw/test.txt"));
+ InputStream stream = mAssetHelper.openResource("raw/test.txt");
Assert.assertNotNull("failed to open resource raw/test.txt", stream);
Assert.assertEquals(readAsString(stream), TEST_STRING);
@@ -70,7 +69,7 @@
@Test
@SmallTest
public void testOpenExistingResourceWithLeadingSlash() {
- InputStream stream = mAssetHelper.openResource(Uri.parse("/raw/test"));
+ InputStream stream = mAssetHelper.openResource("/raw/test");
Assert.assertNotNull("failed to open resource /raw/test.txt with leading slash", stream);
Assert.assertEquals(readAsString(stream), TEST_STRING);
@@ -79,7 +78,7 @@
@Test
@SmallTest
public void testOpenExistingResourceWithNoExtension() {
- InputStream stream = mAssetHelper.openResource(Uri.parse("raw/test"));
+ InputStream stream = mAssetHelper.openResource("raw/test");
Assert.assertNotNull("failed to open resource raw/test with no extension", stream);
Assert.assertEquals(readAsString(stream), TEST_STRING);
@@ -89,19 +88,19 @@
@SmallTest
public void testOpenInvalidResources() {
Assert.assertNull("raw/nonexist_file.html doesn't exist - should fail",
- mAssetHelper.openResource(Uri.parse("raw/nonexist_file.html")));
+ mAssetHelper.openResource("raw/nonexist_file.html"));
Assert.assertNull("test.txt doesn't have a resource type - should fail",
- mAssetHelper.openResource(Uri.parse("test.txt")));
+ mAssetHelper.openResource("test.txt"));
Assert.assertNull("resource with \"/android_res\" prefix should fail",
- mAssetHelper.openResource(Uri.parse("/android_res/raw/test.txt")));
+ mAssetHelper.openResource("/android_res/raw/test.txt"));
}
@Test
@SmallTest
public void testOpenExistingAsset() {
- InputStream stream = mAssetHelper.openAsset(Uri.parse("text/test.txt"));
+ InputStream stream = mAssetHelper.openAsset("text/test.txt");
Assert.assertNotNull("failed to open asset text/test.txt", stream);
Assert.assertEquals(readAsString(stream), TEST_STRING);
@@ -110,7 +109,7 @@
@Test
@SmallTest
public void testOpenExistingAssetWithLeadingSlash() {
- InputStream stream = mAssetHelper.openAsset(Uri.parse("/text/test.txt"));
+ InputStream stream = mAssetHelper.openAsset("/text/test.txt");
Assert.assertNotNull("failed to open asset /text/test.txt with leading slash", stream);
Assert.assertEquals(readAsString(stream), TEST_STRING);
@@ -120,10 +119,10 @@
@SmallTest
public void testOpenInvalidAssets() {
Assert.assertNull("nonexist_file.html doesn't exist - should fail",
- mAssetHelper.openAsset(Uri.parse("nonexist_file.html")));
+ mAssetHelper.openAsset("nonexist_file.html"));
Assert.assertNull("asset with \"/android_asset\" prefix should fail",
- mAssetHelper.openAsset(Uri.parse("/android_asset/test.txt")));
+ mAssetHelper.openAsset("/android_asset/test.txt"));
}
@Test
@@ -140,6 +139,18 @@
@Test
@MediumTest
+ public void testOpenFileNameWhichResemblesUriScheme() throws Throwable {
+ File testFile = new File(mInternalStorageTestDir, "obb/obb:11/test/some_file.txt");
+ WebkitUtils.writeToFile(testFile, TEST_STRING);
+
+ InputStream stream = AssetHelper.openFile(testFile);
+ Assert.assertNotNull("Should be able to open \"" + testFile + "\" from internal storage",
+ stream);
+ Assert.assertEquals(readAsString(stream), TEST_STRING);
+ }
+
+ @Test
+ @MediumTest
public void testOpenNonExistingFileInInternalStorage() throws Throwable {
File testFile = new File(mInternalStorageTestDir, "some/path/to/non_exist_file.txt");
InputStream stream = AssetHelper.openFile(testFile);
@@ -195,10 +206,10 @@
InputStream svgStream = null;
InputStream svgzStream = null;
try {
- svgStream = assertOpen(Uri.parse("star.svg"));
+ svgStream = assertOpen("star.svg");
byte[] expectedData = readFully(svgStream);
- svgzStream = assertOpen(Uri.parse("star.svgz"));
+ svgzStream = assertOpen("star.svgz");
byte[] actualData = readFully(svgzStream);
Assert.assertArrayEquals(
@@ -209,9 +220,9 @@
}
}
- private InputStream assertOpen(Uri uri) {
- InputStream stream = mAssetHelper.openAsset(uri);
- Assert.assertNotNull("Failed to open \"" + uri + "\"", stream);
+ private InputStream assertOpen(String path) {
+ InputStream stream = mAssetHelper.openAsset(path);
+ Assert.assertNotNull("Failed to open \"" + path + "\"", stream);
return stream;
}
diff --git a/webkit/src/main/java/androidx/webkit/ProxyConfig.java b/webkit/src/main/java/androidx/webkit/ProxyConfig.java
index 74adb9c..39c658f 100644
--- a/webkit/src/main/java/androidx/webkit/ProxyConfig.java
+++ b/webkit/src/main/java/androidx/webkit/ProxyConfig.java
@@ -19,11 +19,11 @@
import androidx.annotation.NonNull;
import androidx.annotation.RestrictTo;
import androidx.annotation.StringDef;
-import androidx.core.util.Pair;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.ArrayList;
+import java.util.Collections;
import java.util.List;
import java.util.concurrent.Executor;
@@ -66,22 +66,22 @@
private static final String BYPASS_RULE_SIMPLE_NAMES = "<local>";
private static final String BYPASS_RULE_REMOVE_IMPLICIT = "<-loopback>";
- private List<Pair<String, String>> mProxyRules;
+ private List<ProxyRule> mProxyRules;
private List<String> mBypassRules;
/**
* @hide Internal use only
*/
@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP_PREFIX)
- public ProxyConfig(List<Pair<String, String>> proxyRules, List<String> bypassRules) {
+ public ProxyConfig(List<ProxyRule> proxyRules, List<String> bypassRules) {
mProxyRules = proxyRules;
mBypassRules = bypassRules;
}
/**
- * Returns the current list of proxy rules. Each pair of (String, String) consists of the proxy
- * URL and the URL schemes for which this proxy is used (one of {@code MATCH_HTTP},
- * {@code MATCH_HTTPS}, {@code MATCH_ALL_SCHEMES}).
+ * Returns the current list of proxy rules. Each {@link ProxyRule} object
+ * holds the proxy URL and the URL schemes for which this proxy is used (one of
+ * {@code MATCH_HTTP}, {@code MATCH_HTTPS}, {@code MATCH_ALL_SCHEMES}).
*
* <p>To add new rules use {@link Builder#addProxyRule(String)} or
* {@link Builder#addProxyRule(String, String)}.
@@ -89,8 +89,8 @@
* @return List of proxy rules
*/
@NonNull
- public List<Pair<String, String>> getProxyRules() {
- return mProxyRules;
+ public List<ProxyRule> getProxyRules() {
+ return Collections.unmodifiableList(mProxyRules);
}
/**
@@ -102,7 +102,52 @@
*/
@NonNull
public List<String> getBypassRules() {
- return mBypassRules;
+ return Collections.unmodifiableList(mBypassRules);
+ }
+
+ /**
+ * Class that holds a scheme filter and a proxy URL.
+ */
+ public static final class ProxyRule {
+ private String mSchemeFilter;
+ private String mUrl;
+
+ /**
+ * @hide Internal use only
+ */
+ @RestrictTo(RestrictTo.Scope.LIBRARY)
+ public ProxyRule(@NonNull String schemeFilter, @NonNull String url) {
+ mSchemeFilter = schemeFilter;
+ mUrl = url;
+ }
+
+ /**
+ * @hide Internal use only
+ */
+ @RestrictTo(RestrictTo.Scope.LIBRARY)
+ public ProxyRule(@NonNull String url) {
+ this(ProxyConfig.MATCH_ALL_SCHEMES, url);
+ }
+
+ /**
+ * Returns the {@link String} that represents the scheme filter for this object.
+ *
+ * @return Scheme filter
+ */
+ @NonNull
+ public String getSchemeFilter() {
+ return mSchemeFilter;
+ }
+
+ /**
+ * Returns the {@link String} that represents the proxy URL for this object.
+ *
+ * @return Proxy URL
+ */
+ @NonNull
+ public String getUrl() {
+ return mUrl;
+ }
}
/**
@@ -115,7 +160,7 @@
* connections to be made directly.
*/
public static final class Builder {
- private List<Pair<String, String>> mProxyRules;
+ private List<ProxyRule> mProxyRules;
private List<String> mBypassRules;
/**
@@ -175,7 +220,8 @@
*/
@NonNull
public Builder addProxyRule(@NonNull String proxyUrl) {
- return addProxyRule(proxyUrl, MATCH_ALL_SCHEMES);
+ mProxyRules.add(new ProxyRule(proxyUrl));
+ return this;
}
/**
@@ -191,7 +237,7 @@
@NonNull
public Builder addProxyRule(@NonNull String proxyUrl,
@NonNull @ProxyScheme String schemeFilter) {
- mProxyRules.add(new Pair<>(schemeFilter, proxyUrl));
+ mProxyRules.add(new ProxyRule(schemeFilter, proxyUrl));
return this;
}
@@ -220,7 +266,7 @@
*/
@NonNull
public Builder addDirect(@NonNull @ProxyScheme String schemeFilter) {
- mProxyRules.add(new Pair<>(DIRECT, schemeFilter));
+ mProxyRules.add(new ProxyRule(schemeFilter, DIRECT));
return this;
}
@@ -271,7 +317,7 @@
}
@NonNull
- private List<Pair<String, String>> proxyRules() {
+ private List<ProxyRule> proxyRules() {
return mProxyRules;
}
diff --git a/webkit/src/main/java/androidx/webkit/WebResourceRequestCompat.java b/webkit/src/main/java/androidx/webkit/WebResourceRequestCompat.java
index cb98b23..fc99bcb 100644
--- a/webkit/src/main/java/androidx/webkit/WebResourceRequestCompat.java
+++ b/webkit/src/main/java/androidx/webkit/WebResourceRequestCompat.java
@@ -25,8 +25,6 @@
import androidx.webkit.internal.WebViewFeatureInternal;
import androidx.webkit.internal.WebViewGlueCommunicator;
-// TODO(gsennton) add a test for this class
-
/**
* Compatibility version of {@link WebResourceRequest}.
*/
diff --git a/webkit/src/main/java/androidx/webkit/WebViewAssetLoader.java b/webkit/src/main/java/androidx/webkit/WebViewAssetLoader.java
index 1625d57..a8d0418 100644
--- a/webkit/src/main/java/androidx/webkit/WebViewAssetLoader.java
+++ b/webkit/src/main/java/androidx/webkit/WebViewAssetLoader.java
@@ -159,11 +159,7 @@
@WorkerThread
@Nullable
public WebResourceResponse handle(@NonNull String path) {
- Uri uri = new Uri.Builder()
- .path(path)
- .build();
-
- InputStream is = mAssetHelper.openAsset(uri);
+ InputStream is = mAssetHelper.openAsset(path);
String mimeType = AssetHelper.guessMimeType(path);
return new WebResourceResponse(mimeType, null, is);
}
@@ -209,11 +205,7 @@
@WorkerThread
@Nullable
public WebResourceResponse handle(@NonNull String path) {
- Uri uri = new Uri.Builder()
- .path(path)
- .build();
-
- InputStream is = mAssetHelper.openResource(uri);
+ InputStream is = mAssetHelper.openResource(path);
String mimeType = AssetHelper.guessMimeType(path);
return new WebResourceResponse(mimeType, null, is);
}
diff --git a/webkit/src/main/java/androidx/webkit/WebViewFeature.java b/webkit/src/main/java/androidx/webkit/WebViewFeature.java
index f71c46d..485fc03 100644
--- a/webkit/src/main/java/androidx/webkit/WebViewFeature.java
+++ b/webkit/src/main/java/androidx/webkit/WebViewFeature.java
@@ -407,8 +407,8 @@
/**
* Feature for {@link #isFeatureSupported(String)}.
* This feature covers
- * {@link WebViewCompat#setForceDark(WebSettings, int)} and
- * {@link WebViewCompat#getForceDark(WebSettings)}.
+ * {@link WebSettingsCompat#setForceDark(WebSettings, int)} and
+ * {@link WebSettingsCompat#getForceDark(WebSettings)}.
*
* TODO(amalova): unhide
* @hide
diff --git a/webkit/src/main/java/androidx/webkit/internal/AssetHelper.java b/webkit/src/main/java/androidx/webkit/internal/AssetHelper.java
index e6d5d2e..8e2d65f 100644
--- a/webkit/src/main/java/androidx/webkit/internal/AssetHelper.java
+++ b/webkit/src/main/java/androidx/webkit/internal/AssetHelper.java
@@ -19,7 +19,6 @@
import android.content.Context;
import android.content.res.AssetManager;
import android.content.res.Resources;
-import android.net.Uri;
import android.os.Build;
import android.util.Log;
import android.util.TypedValue;
@@ -32,11 +31,11 @@
import java.io.IOException;
import java.io.InputStream;
import java.net.URLConnection;
-import java.util.List;
import java.util.zip.GZIPInputStream;
/**
- * Handles opening resources and assets.
+ * A Utility class for opening resources, assets and files for
+ * {@link androidx.webkit.WebViewAssetLoader}.
* Forked from the chromuim project org.chromium.android_webview.AndroidProtocolHandler
*/
public class AssetHelper {
@@ -54,21 +53,30 @@
}
@Nullable
- private static InputStream handleSvgzStream(@NonNull Uri uri, @Nullable InputStream stream) {
- if (stream != null && uri.getLastPathSegment().endsWith(".svgz")) {
+ private static InputStream handleSvgzStream(@NonNull String path,
+ @Nullable InputStream stream) {
+ if (stream != null && path.endsWith(".svgz")) {
try {
stream = new GZIPInputStream(stream);
} catch (IOException e) {
- Log.e(TAG, "Error decompressing " + uri + " - " + e.getMessage());
+ Log.e(TAG, "Error decompressing " + path + " - " + e.getMessage());
return null;
}
}
return stream;
}
- private int getFieldId(@NonNull String assetType, @NonNull String assetName) {
+ @NonNull
+ private static String removeLeadingSlash(@NonNull String path) {
+ if (path.length() > 1 && path.charAt(0) == '/') {
+ path = path.substring(1);
+ }
+ return path;
+ }
+
+ private int getFieldId(@NonNull String resourceType, @NonNull String resourceName) {
String packageName = mContext.getPackageName();
- int id = mContext.getResources().getIdentifier(assetName, assetType, packageName);
+ int id = mContext.getResources().getIdentifier(resourceName, resourceType, packageName);
return id;
}
@@ -81,33 +89,37 @@
/**
* Open an InputStream for an Android resource.
*
- * @param uri The uri to load. The path must be of the form "asset_type/asset_name.ext".
- * @return An InputStream to the Android resource.
+ * @param path Path of the form "resource_type/resource_name.ext".
+ * @return An {@link InputStream} to the Android resource or {@code null} if it cannot open the
+ * resource file.
*/
@Nullable
- public InputStream openResource(@NonNull Uri uri) {
- // The path must be of the form "asset_type/asset_name.ext".
- List<String> pathSegments = uri.getPathSegments();
- if (pathSegments.size() != 2) {
- Log.e(TAG, "Incorrect resource path: " + uri);
+ public InputStream openResource(@NonNull String path) {
+ path = removeLeadingSlash(path);
+ // The path must be of the form "resource_type/resource_name.ext".
+ String[] pathSegments = path.split("/");
+ if (pathSegments.length != 2) {
+ Log.e(TAG, "Incorrect resource path: " + path);
return null;
}
- String assetType = pathSegments.get(0);
- String assetName = pathSegments.get(1);
+ String resourceType = pathSegments[0];
+ String resourceName = pathSegments[1];
// Drop the file extension.
- assetName = assetName.split("\\.")[0];
+ resourceName = resourceName.split("\\.")[0];
try {
- int fieldId = getFieldId(assetType, assetName);
+ int fieldId = getFieldId(resourceType, resourceName);
int valueType = getValueType(fieldId);
if (valueType == TypedValue.TYPE_STRING) {
- return handleSvgzStream(uri, mContext.getResources().openRawResource(fieldId));
+ return handleSvgzStream(path, mContext.getResources().openRawResource(fieldId));
} else {
- Log.e(TAG, "Asset not of type string: " + uri);
+ Log.e(TAG,
+ String.format("Expected %s resource to be of TYPE_STRING but was %d",
+ path, valueType));
return null;
}
} catch (Resources.NotFoundException e) {
- Log.e(TAG, "Resource not found from URL: " + uri, e);
+ Log.e(TAG, "Resource not found from the path: " + path, e);
return null;
}
}
@@ -115,21 +127,18 @@
/**
* Open an InputStream for an Android asset.
*
- * @param uri The uri to load.
- * @return An InputStream to the Android asset.
+ * @param path Path to the asset file to load.
+ * @return An {@link InputStream} to the Android asset or {@code null} if it cannot open the
+ * asset file.
*/
@Nullable
- public InputStream openAsset(@NonNull Uri uri) {
- String path = uri.getPath();
- // Strip leading slash if present.
- if (path.length() > 1 && path.charAt(0) == '/') {
- path = path.substring(1);
- }
+ public InputStream openAsset(@NonNull String path) {
+ path = removeLeadingSlash(path);
try {
AssetManager assets = mContext.getAssets();
- return handleSvgzStream(uri, assets.open(path, AssetManager.ACCESS_STREAMING));
+ return handleSvgzStream(path, assets.open(path, AssetManager.ACCESS_STREAMING));
} catch (IOException e) {
- Log.e(TAG, "Unable to open asset URL: " + uri);
+ Log.e(TAG, "Unable to open asset path: " + path);
return null;
}
}
@@ -137,14 +146,14 @@
/**
* Open an {@code InputStream} for a file in application data directories.
*
- * @param file The the file to be opened.
+ * @param file The file to be opened.
* @return An {@code InputStream} for the requested file or {@code null} if an error happens.
*/
@Nullable
public static InputStream openFile(@NonNull File file) {
try {
FileInputStream fis = new FileInputStream(file);
- return handleSvgzStream(Uri.parse(file.getPath()), fis);
+ return handleSvgzStream(file.getPath(), fis);
} catch (IOException e) {
Log.e(TAG, "Error opening the requested file " + file, e);
return null;
diff --git a/webkit/src/main/java/androidx/webkit/internal/ProxyControllerImpl.java b/webkit/src/main/java/androidx/webkit/internal/ProxyControllerImpl.java
index c2b4675..fe8dbc8 100644
--- a/webkit/src/main/java/androidx/webkit/internal/ProxyControllerImpl.java
+++ b/webkit/src/main/java/androidx/webkit/internal/ProxyControllerImpl.java
@@ -17,7 +17,6 @@
package androidx.webkit.internal;
import androidx.annotation.NonNull;
-import androidx.core.util.Pair;
import androidx.webkit.ProxyConfig;
import androidx.webkit.ProxyController;
import androidx.webkit.WebViewFeature;
@@ -39,14 +38,15 @@
WebViewFeatureInternal webViewFeature =
WebViewFeatureInternal.getFeature(WebViewFeature.PROXY_OVERRIDE);
if (webViewFeature.isSupportedByWebView()) {
- List<Pair<String, String>> proxyRulesList = proxyConfig.getProxyRules();
+ List<ProxyConfig.ProxyRule> proxyRulesList = proxyConfig.getProxyRules();
// A 2D String array representation is required by reflection
String[][] proxyRulesArray = new String[proxyRulesList.size()][2];
for (int i = 0; i < proxyRulesList.size(); i++) {
- proxyRulesArray[i][0] = proxyRulesList.get(0).first;
- proxyRulesArray[i][1] = proxyRulesList.get(0).second;
+ proxyRulesArray[i][0] = proxyRulesList.get(0).getSchemeFilter();
+ proxyRulesArray[i][1] = proxyRulesList.get(0).getUrl();
}
+
getBoundaryInterface().setProxyOverride(proxyRulesArray,
proxyConfig.getBypassRules().toArray(new String[0]), listener, executor);
} else {
diff --git a/work/workmanager-testing/src/androidTest/java/androidx/work/testing/TestSchedulerTest.java b/work/workmanager-testing/src/androidTest/java/androidx/work/testing/TestSchedulerTest.java
index 868ce63..83f2d59 100644
--- a/work/workmanager-testing/src/androidTest/java/androidx/work/testing/TestSchedulerTest.java
+++ b/work/workmanager-testing/src/androidTest/java/androidx/work/testing/TestSchedulerTest.java
@@ -20,10 +20,15 @@
import static org.hamcrest.MatcherAssert.assertThat;
import android.content.Context;
+import android.os.Handler;
+import android.os.Looper;
+import android.util.Log;
+import androidx.lifecycle.Observer;
import androidx.test.core.app.ApplicationProvider;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.LargeTest;
+import androidx.work.Configuration;
import androidx.work.Constraints;
import androidx.work.NetworkType;
import androidx.work.OneTimeWorkRequest;
@@ -41,7 +46,11 @@
import org.junit.runner.RunWith;
import java.util.Collections;
+import java.util.List;
+import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutionException;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
@RunWith(AndroidJUnit4.class)
@@ -50,11 +59,18 @@
private Context mContext;
private TestDriver mTestDriver;
+ private Handler mHandler;
@Before
public void setUp() {
mContext = ApplicationProvider.getApplicationContext();
- WorkManagerTestInitHelper.initializeTestWorkManager(mContext);
+ mHandler = new Handler(Looper.getMainLooper());
+ // Don't set the task executor
+ Configuration configuration = new Configuration.Builder()
+ .setExecutor(new SynchronousExecutor())
+ .setMinimumLoggingLevel(Log.DEBUG)
+ .build();
+ WorkManagerTestInitHelper.initializeTestWorkManager(mContext, configuration);
mTestDriver = WorkManagerTestInitHelper.getTestDriver(mContext);
CountingTestWorker.COUNT.set(0);
}
@@ -186,6 +202,106 @@
mTestDriver.setPeriodDelayMet(request.getId());
}
+ @Test
+ @LargeTest
+ public void testWorker_multipleSetInitialDelayMet_noDeadLock()
+ throws InterruptedException, ExecutionException {
+
+ Configuration configuration = new Configuration.Builder()
+ .setMinimumLoggingLevel(Log.DEBUG)
+ .build();
+ WorkManagerTestInitHelper.initializeTestWorkManager(mContext, configuration);
+ mTestDriver = WorkManagerTestInitHelper.getTestDriver(mContext);
+
+ // This should not deadlock
+ final OneTimeWorkRequest request = createWorkRequest();
+ final WorkManager workManager = WorkManager.getInstance(mContext);
+ workManager.enqueue(request).getResult().get();
+ mTestDriver.setInitialDelayMet(request.getId());
+ mTestDriver.setInitialDelayMet(request.getId());
+
+ final CountDownLatch latch = new CountDownLatch(1);
+ // Use the main looper to observe LiveData because we are using a SerialExecutor which is
+ // wrapping a SynchronousExecutor.
+ mHandler.post(new Runnable() {
+ @Override
+ public void run() {
+ workManager.getWorkInfoByIdLiveData(request.getId()).observeForever(
+ new Observer<WorkInfo>() {
+ @Override
+ public void onChanged(WorkInfo workInfo) {
+ if (workInfo != null && workInfo.getState().isFinished()) {
+ latch.countDown();
+ }
+ }
+ });
+ }
+ });
+
+ latch.await(5, TimeUnit.SECONDS);
+ assertThat(latch.getCount(), is(0L));
+ }
+
+ @Test
+ @LargeTest
+ public void testWorker_multipleSetInitialDelayMetMultiThreaded_noDeadLock()
+ throws InterruptedException {
+
+ Configuration configuration = new Configuration.Builder()
+ .setMinimumLoggingLevel(Log.DEBUG)
+ .build();
+ WorkManagerTestInitHelper.initializeTestWorkManager(mContext, configuration);
+ mTestDriver = WorkManagerTestInitHelper.getTestDriver(mContext);
+
+ // This should not deadlock
+ final WorkManager workManager = WorkManager.getInstance(mContext);
+ int numberOfWorkers = 10;
+ final ExecutorService service = Executors.newFixedThreadPool(numberOfWorkers);
+ for (int i = 0; i < numberOfWorkers; i++) {
+ service.submit(new Runnable() {
+ @Override
+ public void run() {
+ final OneTimeWorkRequest request = createWorkRequest();
+ workManager.enqueue(request);
+ mTestDriver.setInitialDelayMet(request.getId());
+ mTestDriver.setInitialDelayMet(request.getId());
+ }
+ });
+ }
+
+ final CountDownLatch latch = new CountDownLatch(1);
+ // Use the main looper to observe LiveData because we are using a SerialExecutor which is
+ // wrapping a SynchronousExecutor.
+ mHandler.post(new Runnable() {
+ @Override
+ public void run() {
+ // Using the implicit tag name.
+ workManager.getWorkInfosByTagLiveData(TestWorker.class.getName()).observeForever(
+ new Observer<List<WorkInfo>>() {
+ @Override
+ public void onChanged(List<WorkInfo> workInfos) {
+ boolean completed = true;
+ if (workInfos != null && !workInfos.isEmpty()) {
+ for (WorkInfo workInfo : workInfos) {
+ if (!workInfo.getState().isFinished()) {
+ completed = false;
+ break;
+ }
+ }
+ }
+ if (completed) {
+ latch.countDown();
+ }
+ }
+ });
+ }
+ });
+
+ latch.await(10, TimeUnit.SECONDS);
+ service.shutdownNow();
+ assertThat(latch.getCount(), is(0L));
+ }
+
private static OneTimeWorkRequest createWorkRequest() {
return new OneTimeWorkRequest.Builder(TestWorker.class).build();
}
diff --git a/work/workmanager-testing/src/main/java/androidx/work/testing/TestScheduler.java b/work/workmanager-testing/src/main/java/androidx/work/testing/TestScheduler.java
index afc4f41..1595350 100644
--- a/work/workmanager-testing/src/main/java/androidx/work/testing/TestScheduler.java
+++ b/work/workmanager-testing/src/main/java/androidx/work/testing/TestScheduler.java
@@ -50,8 +50,6 @@
private final Map<String, InternalWorkState> mPendingWorkStates;
private final Map<String, InternalWorkState> mTerminatedWorkStates;
- private static final Object sLock = new Object();
-
TestScheduler(@NonNull Context context) {
mContext = context;
mPendingWorkStates = new HashMap<>();
@@ -64,28 +62,24 @@
return;
}
- synchronized (sLock) {
- List<String> workSpecIdsToSchedule = new ArrayList<>(workSpecs.length);
- for (WorkSpec workSpec : workSpecs) {
- if (!mPendingWorkStates.containsKey(workSpec.id)) {
- mPendingWorkStates.put(workSpec.id, new InternalWorkState(mContext, workSpec));
- }
- workSpecIdsToSchedule.add(workSpec.id);
+ List<String> workSpecIdsToSchedule = new ArrayList<>(workSpecs.length);
+ for (WorkSpec workSpec : workSpecs) {
+ if (!mPendingWorkStates.containsKey(workSpec.id)) {
+ mPendingWorkStates.put(workSpec.id, new InternalWorkState(mContext, workSpec));
}
- scheduleInternal(workSpecIdsToSchedule);
+ workSpecIdsToSchedule.add(workSpec.id);
}
+ scheduleInternal(workSpecIdsToSchedule);
}
@Override
public void cancel(@NonNull String workSpecId) {
- synchronized (sLock) {
- WorkManagerImpl.getInstance(mContext).stopWork(workSpecId);
- mPendingWorkStates.remove(workSpecId);
- // We don't need to keep track of cancelled workSpecs. This is because subsequent calls
- // to enqueue() will no-op because insertWorkSpec in WorkDatabase has a conflict
- // policy of @Ignore. So TestScheduler will _never_ be asked to schedule those
- // WorkSpecs.
- }
+ // We don't need to keep track of cancelled workSpecs. This is because subsequent calls
+ // to enqueue() will no-op because insertWorkSpec in WorkDatabase has a conflict
+ // policy of @Ignore. So TestScheduler will _never_ be asked to schedule those
+ // WorkSpecs.
+ WorkManagerImpl.getInstance(mContext).stopWork(workSpecId);
+ mPendingWorkStates.remove(workSpecId);
}
/**
@@ -96,17 +90,15 @@
* @throws IllegalArgumentException if {@code workSpecId} is not enqueued
*/
void setAllConstraintsMet(@NonNull UUID workSpecId) {
- synchronized (sLock) {
- String id = workSpecId.toString();
- if (!mTerminatedWorkStates.containsKey(id)) {
- InternalWorkState internalWorkState = mPendingWorkStates.get(id);
- if (internalWorkState == null) {
- throw new IllegalArgumentException(
- "Work with id " + workSpecId + " is not enqueued!");
- }
- internalWorkState.mConstraintsMet = true;
- scheduleInternal(Collections.singletonList(workSpecId.toString()));
+ String id = workSpecId.toString();
+ if (!mTerminatedWorkStates.containsKey(id)) {
+ InternalWorkState internalWorkState = mPendingWorkStates.get(id);
+ if (internalWorkState == null) {
+ throw new IllegalArgumentException(
+ "Work with id " + workSpecId + " is not enqueued!");
}
+ internalWorkState.mConstraintsMet = true;
+ scheduleInternal(Collections.singletonList(workSpecId.toString()));
}
}
@@ -118,17 +110,15 @@
* @throws IllegalArgumentException if {@code workSpecId} is not enqueued
*/
void setInitialDelayMet(@NonNull UUID workSpecId) {
- synchronized (sLock) {
- String id = workSpecId.toString();
- if (!mTerminatedWorkStates.containsKey(id)) {
- InternalWorkState internalWorkState = mPendingWorkStates.get(id);
- if (internalWorkState == null) {
- throw new IllegalArgumentException(
- "Work with id " + workSpecId + " is not enqueued!");
- }
- internalWorkState.mInitialDelayMet = true;
- scheduleInternal(Collections.singletonList(workSpecId.toString()));
+ String id = workSpecId.toString();
+ if (!mTerminatedWorkStates.containsKey(id)) {
+ InternalWorkState internalWorkState = mPendingWorkStates.get(id);
+ if (internalWorkState == null) {
+ throw new IllegalArgumentException(
+ "Work with id " + workSpecId + " is not enqueued!");
}
+ internalWorkState.mInitialDelayMet = true;
+ scheduleInternal(Collections.singletonList(workSpecId.toString()));
}
}
@@ -140,29 +130,25 @@
* @throws IllegalArgumentException if {@code workSpecId} is not enqueued
*/
void setPeriodDelayMet(@NonNull UUID workSpecId) {
- synchronized (sLock) {
- String id = workSpecId.toString();
- InternalWorkState internalWorkState = mPendingWorkStates.get(id);
- if (internalWorkState == null) {
- throw new IllegalArgumentException(
- "Work with id " + workSpecId + " is not enqueued!");
- }
- internalWorkState.mPeriodDelayMet = true;
- scheduleInternal(Collections.singletonList(workSpecId.toString()));
+ String id = workSpecId.toString();
+ InternalWorkState internalWorkState = mPendingWorkStates.get(id);
+ if (internalWorkState == null) {
+ throw new IllegalArgumentException(
+ "Work with id " + workSpecId + " is not enqueued!");
}
+ internalWorkState.mPeriodDelayMet = true;
+ scheduleInternal(Collections.singletonList(workSpecId.toString()));
}
@Override
public void onExecuted(@NonNull String workSpecId, boolean needsReschedule) {
- synchronized (sLock) {
- InternalWorkState internalWorkState = mPendingWorkStates.get(workSpecId);
- if (internalWorkState != null) {
- if (internalWorkState.mWorkSpec.isPeriodic()) {
- internalWorkState.reset();
- } else {
- mTerminatedWorkStates.put(workSpecId, internalWorkState);
- mPendingWorkStates.remove(workSpecId);
- }
+ InternalWorkState internalWorkState = mPendingWorkStates.get(workSpecId);
+ if (internalWorkState != null) {
+ if (internalWorkState.mWorkSpec.isPeriodic()) {
+ internalWorkState.reset();
+ } else {
+ mTerminatedWorkStates.put(workSpecId, internalWorkState);
+ mPendingWorkStates.remove(workSpecId);
}
}
}
diff --git a/work/workmanager-testing/src/main/java/androidx/work/testing/TestWorkManagerImpl.java b/work/workmanager-testing/src/main/java/androidx/work/testing/TestWorkManagerImpl.java
index 4e7c916..bb84e45 100644
--- a/work/workmanager-testing/src/main/java/androidx/work/testing/TestWorkManagerImpl.java
+++ b/work/workmanager-testing/src/main/java/androidx/work/testing/TestWorkManagerImpl.java
@@ -49,6 +49,14 @@
// Note: This implies that the call to ForceStopRunnable() actually does nothing.
// This is okay when testing.
+
+ // IMPORTANT: Leave the main thread executor as a Direct executor. This is very important.
+ // Otherwise we subtly change the order of callbacks. onExecuted() will execute after
+ // a call to StopWorkRunnable(). StopWorkRunnable() removes the pending WorkSpec and
+ // therefore the call to onExecuted() does not add the workSpecId to the list of
+ // terminated WorkSpecs. This is because internalWorkState == null.
+ // Also for PeriodicWorkRequests, Schedulers.schedule() will run before the call to
+ // onExecuted() and therefore PeriodicWorkRequests will always run twice.
super(
context,
configuration,
diff --git a/work/workmanager/src/androidTest/java/androidx/work/impl/WorkerWrapperTest.java b/work/workmanager/src/androidTest/java/androidx/work/impl/WorkerWrapperTest.java
index 20121dc..b54ac08 100644
--- a/work/workmanager/src/androidTest/java/androidx/work/impl/WorkerWrapperTest.java
+++ b/work/workmanager/src/androidTest/java/androidx/work/impl/WorkerWrapperTest.java
@@ -180,7 +180,7 @@
assertThat(mWorkSpecDao.getState(work.getStringId()), is(FAILED));
}
- @Test
+ @Test(expected = IllegalStateException.class)
@SmallTest
public void testUsedWorker_failsExecution() {
OneTimeWorkRequest work = new OneTimeWorkRequest.Builder(TestWorker.class).build();
@@ -205,7 +205,6 @@
.withWorker(usedWorker)
.build();
workerWrapper.run();
- assertThat(mWorkSpecDao.getState(work.getStringId()), is(FAILED));
}
@Test
diff --git a/work/workmanager/src/androidTest/java/androidx/work/impl/background/systemjob/SystemJobInfoConverterTest.java b/work/workmanager/src/androidTest/java/androidx/work/impl/background/systemjob/SystemJobInfoConverterTest.java
index 037ec16..a36e628 100644
--- a/work/workmanager/src/androidTest/java/androidx/work/impl/background/systemjob/SystemJobInfoConverterTest.java
+++ b/work/workmanager/src/androidTest/java/androidx/work/impl/background/systemjob/SystemJobInfoConverterTest.java
@@ -218,6 +218,25 @@
convertWithRequiredNetworkType(METERED, JobInfo.NETWORK_TYPE_METERED, 26);
}
+ @Test
+ @SmallTest
+ @SdkSuppress(minSdkVersion = 29)
+ public void testConvert_setImportantWhileForeground() {
+ WorkSpec workSpec = getTestWorkSpecWithConstraints(new Constraints.Builder().build());
+ JobInfo jobInfo = mConverter.convert(workSpec, JOB_ID);
+ assertThat(jobInfo.isImportantWhileForeground(), is(true));
+ }
+
+ @Test
+ @SmallTest
+ @SdkSuppress(minSdkVersion = 29)
+ public void testConvert_setImportantWhileForeground_withTimingConstraints() {
+ WorkSpec workSpec = new WorkSpec("id", TestWorker.class.getName());
+ workSpec.setPeriodic(TEST_INTERVAL_DURATION, TEST_FLEX_DURATION);
+ JobInfo jobInfo = mConverter.convert(workSpec, JOB_ID);
+ assertThat(jobInfo.isImportantWhileForeground(), is(false));
+ }
+
private void convertWithRequiredNetworkType(NetworkType networkType,
int jobInfoNetworkType,
int minSdkVersion) {
diff --git a/work/workmanager/src/main/java/androidx/work/WorkerFactory.java b/work/workmanager/src/main/java/androidx/work/WorkerFactory.java
index 3f7d8ee..b2abb54 100644
--- a/work/workmanager/src/main/java/androidx/work/WorkerFactory.java
+++ b/work/workmanager/src/main/java/androidx/work/WorkerFactory.java
@@ -44,8 +44,9 @@
* null} so it can delegate to the default {@link WorkerFactory}.
* <p></p>
* Returns a new instance of the specified {@code workerClassName} given the arguments. The
- * returned worker should be a newly-created instance and must not have been previously returned
- * or used by WorkManager.
+ * returned worker must be a newly-created instance and must not have been previously returned
+ * or invoked by WorkManager. Otherwise, WorkManager will throw an
+ * {@link IllegalStateException}.
*
* @param appContext The application context
* @param workerClassName The class name of the worker to create
@@ -64,11 +65,13 @@
* the current ClassLoader. The returned worker should be a newly-created instance and must not
* have been previously returned or used by WorkManager.
*
- * @param appContext The application context
- * @param workerClassName The class name of the worker to create
+ * @param appContext The application context
+ * @param workerClassName The class name of the worker to create
* @param workerParameters Parameters for worker initialization
* @return A new {@link ListenableWorker} instance of type {@code workerClassName}, or
- * {@code null} if the worker could not be created
+ * {@code null} if the worker could not be created
+ * @throws IllegalStateException when the {@link WorkerFactory} returns an instance
+ * of the {@link ListenableWorker} which is used.
* @hide
*/
@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
@@ -77,31 +80,40 @@
@NonNull String workerClassName,
@NonNull WorkerParameters workerParameters) {
- ListenableWorker worker;
- worker = createWorker(appContext, workerClassName, workerParameters);
- if (worker != null) {
- return worker;
+ ListenableWorker worker = createWorker(appContext, workerClassName, workerParameters);
+ if (worker == null) {
+ // Fallback to reflection
+ Class<? extends ListenableWorker> clazz = null;
+ try {
+ clazz = Class.forName(workerClassName).asSubclass(ListenableWorker.class);
+ } catch (ClassNotFoundException e) {
+ Logger.get().error(TAG, "Class not found: " + workerClassName);
+ }
+ if (clazz != null) {
+ try {
+ Constructor<? extends ListenableWorker> constructor =
+ clazz.getDeclaredConstructor(Context.class, WorkerParameters.class);
+ worker = constructor.newInstance(
+ appContext,
+ workerParameters);
+ } catch (Exception e) {
+ Logger.get().error(TAG, "Could not instantiate " + workerClassName, e);
+ }
+ }
}
- Class<? extends ListenableWorker> clazz;
- try {
- clazz = Class.forName(workerClassName).asSubclass(ListenableWorker.class);
- } catch (ClassNotFoundException e) {
- Logger.get().error(TAG, "Class not found: " + workerClassName);
- return null;
+ if (worker != null && worker.isUsed()) {
+ String factoryName = this.getClass().getName();
+ String message = String.format("WorkerFactory (%s) returned an instance of a "
+ + "ListenableWorker (%s) which has already been invoked. "
+ + "createWorker() must always return a new instance of a "
+ + "ListenableWorker.",
+ factoryName, workerClassName);
+
+ throw new IllegalStateException(message);
}
- try {
- Constructor<? extends ListenableWorker> constructor =
- clazz.getDeclaredConstructor(Context.class, WorkerParameters.class);
- worker = constructor.newInstance(
- appContext,
- workerParameters);
- return worker;
- } catch (Exception e) {
- Logger.get().error(TAG, "Could not instantiate " + workerClassName, e);
- }
- return null;
+ return worker;
}
/**
@@ -109,9 +121,8 @@
* @hide
*/
@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
- public static WorkerFactory getDefaultWorkerFactory() {
+ public static @NonNull WorkerFactory getDefaultWorkerFactory() {
return new WorkerFactory() {
-
@Override
public @Nullable ListenableWorker createWorker(
@NonNull Context appContext,
diff --git a/work/workmanager/src/main/java/androidx/work/impl/background/systemjob/SystemJobInfoConverter.java b/work/workmanager/src/main/java/androidx/work/impl/background/systemjob/SystemJobInfoConverter.java
index d78a9d9..224d23c 100644
--- a/work/workmanager/src/main/java/androidx/work/impl/background/systemjob/SystemJobInfoConverter.java
+++ b/work/workmanager/src/main/java/androidx/work/impl/background/systemjob/SystemJobInfoConverter.java
@@ -89,10 +89,19 @@
long now = System.currentTimeMillis();
long offset = Math.max(nextRunTime - now, 0);
- // Even if a WorkRequest has no constraints, setMinimumLatency(0) still needs to be
- // called due to an issue in JobInfo.Builder#build and JobInfo with no constraints. See
- // b/67716867.
- builder.setMinimumLatency(offset);
+ if (Build.VERSION.SDK_INT <= 28) {
+ // Before API 29, Jobs needed at least one constraint. Therefore before API 29 we
+ // always setMinimumLatency to make sure we have at least one constraint.
+ // See aosp/5434530 & b/6771687
+ builder.setMinimumLatency(offset);
+ } else {
+ if (offset > 0) {
+ // Only set a minimum latency when applicable.
+ builder.setMinimumLatency(offset);
+ } else {
+ builder.setImportantWhileForeground(true);
+ }
+ }
if (Build.VERSION.SDK_INT >= 24 && constraints.hasContentUriTriggers()) {
ContentUriTriggers contentUriTriggers = constraints.getContentUriTriggers();