Merge "Correctly passing IWatchFaceCommand binder in a bundle" into androidx-master-dev
diff --git a/.idea/codeStyles/Project.xml b/.idea/codeStyles/Project.xml
index d522e82..3c36743 100644
--- a/.idea/codeStyles/Project.xml
+++ b/.idea/codeStyles/Project.xml
@@ -259,4 +259,4 @@
<option name="CODE_STYLE_DEFAULTS" value="KOTLIN_OFFICIAL" />
</codeStyleSettings>
</code_scheme>
-</component>
\ No newline at end of file
+</component>
diff --git a/README.md b/README.md
index c59cb68..6dedcfd 100644
--- a/README.md
+++ b/README.md
@@ -12,7 +12,7 @@
For contributions via GitHub, see the [GitHub Contribution Guide](CONTRIBUTING.md).
-Note: The contributions workflow via GitHub is currently experimental - only contributions to [Room](room) and [Workmanager](work) are being accepted at this time.
+Note: The contributions workflow via GitHub is currently experimental - only contributions to [Paging](paging), [Room](room) and [WorkManager](work) are being accepted at this time.
## Code Review Etiquette
When contributing to Jetpack, follow the [code review etiquette](code-review.md).
@@ -76,7 +76,7 @@
./studiow
```
-and accept the license agreement when prompted. Now you're ready edit, run, and test!
+and accept the license agreement when prompted. Now you're ready to edit, run, and test!
If you get “Unregistered VCS root detected” click “Add root” to enable git integration for Android Studio.
diff --git a/activity/activity-ktx/build.gradle b/activity/activity-ktx/build.gradle
index 56ef830..0fb2f0d 100644
--- a/activity/activity-ktx/build.gradle
+++ b/activity/activity-ktx/build.gradle
@@ -34,6 +34,9 @@
because 'Mirror activity dependency graph for -ktx artifacts'
}
api(project(":lifecycle:lifecycle-viewmodel-ktx"))
+ api(project(":savedstate:savedstate-ktx")) {
+ because 'Mirror activity dependency graph for -ktx artifacts'
+ }
api(KOTLIN_STDLIB)
androidTestImplementation(project(":lifecycle:lifecycle-runtime-testing"))
diff --git a/activity/activity/api/api_lint.ignore b/activity/activity/api/api_lint.ignore
index 7081284..e49fd6b 100644
--- a/activity/activity/api/api_lint.ignore
+++ b/activity/activity/api/api_lint.ignore
@@ -1,4 +1,8 @@
// Baseline format: 1.0
+CallbackMethodName: androidx.activity.OnBackPressedCallback#handleOnBackPressed():
+ Callback method names must follow the on<Something> style: handleOnBackPressed
+
+
ExecutorRegistration: androidx.activity.OnBackPressedDispatcher#addCallback(androidx.lifecycle.LifecycleOwner, androidx.activity.OnBackPressedCallback):
Registration methods should have overload that accepts delivery Executor: `addCallback`
ExecutorRegistration: androidx.activity.contextaware.ContextAware#addOnContextAvailableListener(androidx.activity.contextaware.OnContextAvailableListener):
diff --git a/activity/activity/build.gradle b/activity/activity/build.gradle
index fada015..ed1b9d8 100644
--- a/activity/activity/build.gradle
+++ b/activity/activity/build.gradle
@@ -25,7 +25,7 @@
api("androidx.core:core:1.1.0")
api(project(":lifecycle:lifecycle-runtime"))
api(project(":lifecycle:lifecycle-viewmodel"))
- api("androidx.savedstate:savedstate:1.1.0-alpha01")
+ api(project(":savedstate:savedstate"))
api(project(":lifecycle:lifecycle-viewmodel-savedstate"))
androidTestImplementation(project(":lifecycle:lifecycle-runtime-testing"))
diff --git a/annotation/annotation/api/current.txt b/annotation/annotation/api/current.txt
index 466cec5..3c5ed6b 100644
--- a/annotation/annotation/api/current.txt
+++ b/annotation/annotation/api/current.txt
@@ -61,6 +61,9 @@
field public static final int SP = 2; // 0x2
}
+ @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) @java.lang.annotation.Target({java.lang.annotation.ElementType.METHOD}) public @interface DoNotInline {
+ }
+
@java.lang.annotation.Documented @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) @java.lang.annotation.Target({java.lang.annotation.ElementType.METHOD, java.lang.annotation.ElementType.PARAMETER, java.lang.annotation.ElementType.FIELD, java.lang.annotation.ElementType.LOCAL_VARIABLE}) public @interface DrawableRes {
}
diff --git a/annotation/annotation/api/public_plus_experimental_current.txt b/annotation/annotation/api/public_plus_experimental_current.txt
index 466cec5..3c5ed6b 100644
--- a/annotation/annotation/api/public_plus_experimental_current.txt
+++ b/annotation/annotation/api/public_plus_experimental_current.txt
@@ -61,6 +61,9 @@
field public static final int SP = 2; // 0x2
}
+ @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) @java.lang.annotation.Target({java.lang.annotation.ElementType.METHOD}) public @interface DoNotInline {
+ }
+
@java.lang.annotation.Documented @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) @java.lang.annotation.Target({java.lang.annotation.ElementType.METHOD, java.lang.annotation.ElementType.PARAMETER, java.lang.annotation.ElementType.FIELD, java.lang.annotation.ElementType.LOCAL_VARIABLE}) public @interface DrawableRes {
}
diff --git a/annotation/annotation/api/restricted_current.txt b/annotation/annotation/api/restricted_current.txt
index 466cec5..3c5ed6b 100644
--- a/annotation/annotation/api/restricted_current.txt
+++ b/annotation/annotation/api/restricted_current.txt
@@ -61,6 +61,9 @@
field public static final int SP = 2; // 0x2
}
+ @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) @java.lang.annotation.Target({java.lang.annotation.ElementType.METHOD}) public @interface DoNotInline {
+ }
+
@java.lang.annotation.Documented @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) @java.lang.annotation.Target({java.lang.annotation.ElementType.METHOD, java.lang.annotation.ElementType.PARAMETER, java.lang.annotation.ElementType.FIELD, java.lang.annotation.ElementType.LOCAL_VARIABLE}) public @interface DrawableRes {
}
diff --git a/annotation/annotation/src/main/java/androidx/annotation/DoNotInline.java b/annotation/annotation/src/main/java/androidx/annotation/DoNotInline.java
new file mode 100644
index 0000000..3ae3fa4
--- /dev/null
+++ b/annotation/annotation/src/main/java/androidx/annotation/DoNotInline.java
@@ -0,0 +1,41 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package androidx.annotation;
+
+import static java.lang.annotation.ElementType.METHOD;
+import static java.lang.annotation.RetentionPolicy.CLASS;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.Target;
+
+/**
+ * Denotes that the annotated method should not be inlined when
+ * the code is optimized at build time. This is typically used
+ * to avoid inlining purposely out-of-line methods that are
+ * intended to be in separate classes.
+ * <p>
+ * Example:
+ * <pre><code>
+ * @DoNotInline
+ * public void foo() {
+ * ...
+ * }
+ * </code></pre>
+ */
+@Retention(CLASS)
+@Target({METHOD})
+public @interface DoNotInline {
+}
diff --git a/annotation/annotation/src/main/resources/META-INF/proguard/androidx-annotations.pro b/annotation/annotation/src/main/resources/META-INF/proguard/androidx-annotations.pro
index cd1cd53..4b58b5f 100644
--- a/annotation/annotation/src/main/resources/META-INF/proguard/androidx-annotations.pro
+++ b/annotation/annotation/src/main/resources/META-INF/proguard/androidx-annotations.pro
@@ -12,3 +12,7 @@
-keepclasseswithmembers class * {
@androidx.annotation.Keep <init>(...);
}
+
+-keepclassmembers,allowobfuscation class * {
+ @androidx.annotation.DoNotInline <methods>;
+}
diff --git a/appcompat/appcompat-lint/src/main/kotlin/androidx/appcompat/res/DrawableLoadingDetector.kt b/appcompat/appcompat-lint/src/main/kotlin/androidx/appcompat/res/DrawableLoadingDetector.kt
index 4765881..77a19bb 100644
--- a/appcompat/appcompat-lint/src/main/kotlin/androidx/appcompat/res/DrawableLoadingDetector.kt
+++ b/appcompat/appcompat-lint/src/main/kotlin/androidx/appcompat/res/DrawableLoadingDetector.kt
@@ -25,14 +25,14 @@
import com.android.tools.lint.detector.api.Severity
// Flags usage of Context.getDrawable and Resources.getDrawable and suggests converting them
-// to either ContextCompat.getDrawable or ResourcesCompat.getDrawable
+// to either AppCompatResources.getDrawable or ResourcesCompat.getDrawable
@Suppress("UnstableApiUsage")
class DrawableLoadingDetector : BaseMethodDeprecationDetector(
NOT_USING_COMPAT_LOADING,
// Suggest using ContextCompat.getDrawable
DeprecationCondition(
MethodLocation("android.content.Context", "getDrawable", TYPE_INT),
- "Use `ContextCompat.getDrawable()`"
+ "Use `AppCompatResources.getDrawable()`"
),
// Suggest using ResourcesCompat.getDrawable for one-parameter Resources.getDrawable calls
DeprecationCondition(
diff --git a/appcompat/appcompat-lint/src/test/kotlin/androidx/appcompat/lint/res/DrawableLoadingDetectorTest.kt b/appcompat/appcompat-lint/src/test/kotlin/androidx/appcompat/lint/res/DrawableLoadingDetectorTest.kt
index de28066..a587f5f 100644
--- a/appcompat/appcompat-lint/src/test/kotlin/androidx/appcompat/lint/res/DrawableLoadingDetectorTest.kt
+++ b/appcompat/appcompat-lint/src/test/kotlin/androidx/appcompat/lint/res/DrawableLoadingDetectorTest.kt
@@ -85,7 +85,7 @@
).issues(DrawableLoadingDetector.NOT_USING_COMPAT_LOADING)
.run()
.expect("""
-src/com/example/CustomActivity.kt:9: Warning: Use ContextCompat.getDrawable() [UseCompatLoadingForDrawables]
+src/com/example/CustomActivity.kt:9: Warning: Use AppCompatResources.getDrawable() [UseCompatLoadingForDrawables]
getDrawable(android.R.drawable.ic_delete)
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
0 errors, 1 warnings
diff --git a/appcompat/appcompat/build.gradle b/appcompat/appcompat/build.gradle
index 1afec62..36ef6e4 100644
--- a/appcompat/appcompat/build.gradle
+++ b/appcompat/appcompat/build.gradle
@@ -23,7 +23,7 @@
api("androidx.drawerlayout:drawerlayout:1.0.0")
implementation(project(":lifecycle:lifecycle-runtime"))
implementation(project(":lifecycle:lifecycle-viewmodel"))
- api("androidx.savedstate:savedstate:1.1.0-alpha01")
+ api(project(":savedstate:savedstate"))
androidTestImplementation(KOTLIN_STDLIB)
androidTestImplementation(ANDROIDX_TEST_EXT_JUNIT)
diff --git a/appcompat/appcompat/src/main/java/androidx/appcompat/app/AppCompatDelegateImpl.java b/appcompat/appcompat/src/main/java/androidx/appcompat/app/AppCompatDelegateImpl.java
index 74b96b7..2e08c87 100644
--- a/appcompat/appcompat/src/main/java/androidx/appcompat/app/AppCompatDelegateImpl.java
+++ b/appcompat/appcompat/src/main/java/androidx/appcompat/app/AppCompatDelegateImpl.java
@@ -2710,7 +2710,7 @@
} else if (mActionModeView.getParent() instanceof View) {
ViewCompat.requestApplyInsets((View) mActionModeView.getParent());
}
- mActionModeView.removeAllViews();
+ mActionModeView.killMode();
mFadeAnim.setListener(null);
mFadeAnim = null;
ViewCompat.requestApplyInsets(mSubDecor);
diff --git a/appcompat/appcompat/src/main/java/androidx/appcompat/widget/ActionBarContextView.java b/appcompat/appcompat/src/main/java/androidx/appcompat/widget/ActionBarContextView.java
index 996ce82..ef72997 100644
--- a/appcompat/appcompat/src/main/java/androidx/appcompat/widget/ActionBarContextView.java
+++ b/appcompat/appcompat/src/main/java/androidx/appcompat/widget/ActionBarContextView.java
@@ -45,6 +45,7 @@
private CharSequence mSubtitle;
private View mClose;
+ private View mCloseButton;
private View mCustomView;
private LinearLayout mTitleLayout;
private TextView mTitleView;
@@ -167,8 +168,8 @@
addView(mClose);
}
- View closeButton = mClose.findViewById(R.id.action_mode_close_button);
- closeButton.setOnClickListener(new OnClickListener() {
+ mCloseButton = mClose.findViewById(R.id.action_mode_close_button);
+ mCloseButton.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
mode.finish();
@@ -201,6 +202,10 @@
removeAllViews();
mCustomView = null;
mMenuView = null;
+ mActionMenuPresenter = null;
+ if (mCloseButton != null) {
+ mCloseButton.setOnClickListener(null);
+ }
}
@Override
diff --git a/appsearch/.gitignore b/appsearch/.gitignore
new file mode 100644
index 0000000..3166b90
--- /dev/null
+++ b/appsearch/.gitignore
@@ -0,0 +1 @@
+.cxx/
diff --git a/appsearch/appsearch/.gitignore b/appsearch/appsearch/.gitignore
deleted file mode 100644
index c6b1c0b..0000000
--- a/appsearch/appsearch/.gitignore
+++ /dev/null
@@ -1 +0,0 @@
-/.cxx/
diff --git a/appsearch/appsearch/api/current.txt b/appsearch/appsearch/api/current.txt
index 0e7f3c8..1c5f6a9 100644
--- a/appsearch/appsearch/api/current.txt
+++ b/appsearch/appsearch/api/current.txt
@@ -38,6 +38,9 @@
}
public class AppSearchManager {
+ method public com.google.common.util.concurrent.ListenableFuture<androidx.appsearch.app.AppSearchBatchResult<java.lang.String!,androidx.appsearch.app.GenericDocument!>!> getDocuments(androidx.appsearch.app.AppSearchManager.GetDocumentsRequest);
+ method public com.google.common.util.concurrent.ListenableFuture<androidx.appsearch.app.AppSearchBatchResult<java.lang.String!,java.lang.Void!>!> putDocuments(androidx.appsearch.app.AppSearchManager.PutDocumentsRequest);
+ method public com.google.common.util.concurrent.ListenableFuture<androidx.appsearch.app.AppSearchResult<java.lang.Void!>!> setSchema(androidx.appsearch.app.AppSearchManager.SetSchemaRequest);
field public static final String DEFAULT_DATABASE_NAME = "";
}
@@ -47,6 +50,42 @@
method public androidx.appsearch.app.AppSearchManager.Builder setDatabaseName(String);
}
+ public static final class AppSearchManager.GetDocumentsRequest {
+ }
+
+ public static final class AppSearchManager.GetDocumentsRequest.Builder {
+ ctor public AppSearchManager.GetDocumentsRequest.Builder();
+ method public androidx.appsearch.app.AppSearchManager.GetDocumentsRequest.Builder addUris(java.lang.String!...);
+ method public androidx.appsearch.app.AppSearchManager.GetDocumentsRequest.Builder addUris(java.util.Collection<java.lang.String!>);
+ method public androidx.appsearch.app.AppSearchManager.GetDocumentsRequest build();
+ method public androidx.appsearch.app.AppSearchManager.GetDocumentsRequest.Builder setNamespace(String);
+ }
+
+ public static final class AppSearchManager.PutDocumentsRequest {
+ }
+
+ public static final class AppSearchManager.PutDocumentsRequest.Builder {
+ ctor public AppSearchManager.PutDocumentsRequest.Builder();
+ method public androidx.appsearch.app.AppSearchManager.PutDocumentsRequest.Builder addDataClass(java.lang.Object!...) throws androidx.appsearch.exceptions.AppSearchException;
+ method public androidx.appsearch.app.AppSearchManager.PutDocumentsRequest.Builder addDataClass(java.util.Collection<java.lang.Object!>) throws androidx.appsearch.exceptions.AppSearchException;
+ method public androidx.appsearch.app.AppSearchManager.PutDocumentsRequest.Builder addGenericDocument(androidx.appsearch.app.GenericDocument!...);
+ method public androidx.appsearch.app.AppSearchManager.PutDocumentsRequest.Builder addGenericDocument(java.util.Collection<androidx.appsearch.app.GenericDocument!>);
+ method public androidx.appsearch.app.AppSearchManager.PutDocumentsRequest build();
+ }
+
+ public static final class AppSearchManager.SetSchemaRequest {
+ }
+
+ public static final class AppSearchManager.SetSchemaRequest.Builder {
+ ctor public AppSearchManager.SetSchemaRequest.Builder();
+ method public androidx.appsearch.app.AppSearchManager.SetSchemaRequest.Builder addDataClass(Class<?>!...) throws androidx.appsearch.exceptions.AppSearchException;
+ method public androidx.appsearch.app.AppSearchManager.SetSchemaRequest.Builder addDataClass(java.util.Collection<java.lang.Class<?>!>) throws androidx.appsearch.exceptions.AppSearchException;
+ method public androidx.appsearch.app.AppSearchManager.SetSchemaRequest.Builder addSchema(androidx.appsearch.app.AppSearchSchema!...);
+ method public androidx.appsearch.app.AppSearchManager.SetSchemaRequest.Builder addSchema(java.util.Collection<androidx.appsearch.app.AppSearchSchema!>);
+ method public androidx.appsearch.app.AppSearchManager.SetSchemaRequest build();
+ method public androidx.appsearch.app.AppSearchManager.SetSchemaRequest.Builder setForceOverride(boolean);
+ }
+
public final class AppSearchResult<ValueType> {
method public String? getErrorMessage();
method public int getResultCode();
diff --git a/appsearch/appsearch/api/public_plus_experimental_current.txt b/appsearch/appsearch/api/public_plus_experimental_current.txt
index 0e7f3c8..1c5f6a9 100644
--- a/appsearch/appsearch/api/public_plus_experimental_current.txt
+++ b/appsearch/appsearch/api/public_plus_experimental_current.txt
@@ -38,6 +38,9 @@
}
public class AppSearchManager {
+ method public com.google.common.util.concurrent.ListenableFuture<androidx.appsearch.app.AppSearchBatchResult<java.lang.String!,androidx.appsearch.app.GenericDocument!>!> getDocuments(androidx.appsearch.app.AppSearchManager.GetDocumentsRequest);
+ method public com.google.common.util.concurrent.ListenableFuture<androidx.appsearch.app.AppSearchBatchResult<java.lang.String!,java.lang.Void!>!> putDocuments(androidx.appsearch.app.AppSearchManager.PutDocumentsRequest);
+ method public com.google.common.util.concurrent.ListenableFuture<androidx.appsearch.app.AppSearchResult<java.lang.Void!>!> setSchema(androidx.appsearch.app.AppSearchManager.SetSchemaRequest);
field public static final String DEFAULT_DATABASE_NAME = "";
}
@@ -47,6 +50,42 @@
method public androidx.appsearch.app.AppSearchManager.Builder setDatabaseName(String);
}
+ public static final class AppSearchManager.GetDocumentsRequest {
+ }
+
+ public static final class AppSearchManager.GetDocumentsRequest.Builder {
+ ctor public AppSearchManager.GetDocumentsRequest.Builder();
+ method public androidx.appsearch.app.AppSearchManager.GetDocumentsRequest.Builder addUris(java.lang.String!...);
+ method public androidx.appsearch.app.AppSearchManager.GetDocumentsRequest.Builder addUris(java.util.Collection<java.lang.String!>);
+ method public androidx.appsearch.app.AppSearchManager.GetDocumentsRequest build();
+ method public androidx.appsearch.app.AppSearchManager.GetDocumentsRequest.Builder setNamespace(String);
+ }
+
+ public static final class AppSearchManager.PutDocumentsRequest {
+ }
+
+ public static final class AppSearchManager.PutDocumentsRequest.Builder {
+ ctor public AppSearchManager.PutDocumentsRequest.Builder();
+ method public androidx.appsearch.app.AppSearchManager.PutDocumentsRequest.Builder addDataClass(java.lang.Object!...) throws androidx.appsearch.exceptions.AppSearchException;
+ method public androidx.appsearch.app.AppSearchManager.PutDocumentsRequest.Builder addDataClass(java.util.Collection<java.lang.Object!>) throws androidx.appsearch.exceptions.AppSearchException;
+ method public androidx.appsearch.app.AppSearchManager.PutDocumentsRequest.Builder addGenericDocument(androidx.appsearch.app.GenericDocument!...);
+ method public androidx.appsearch.app.AppSearchManager.PutDocumentsRequest.Builder addGenericDocument(java.util.Collection<androidx.appsearch.app.GenericDocument!>);
+ method public androidx.appsearch.app.AppSearchManager.PutDocumentsRequest build();
+ }
+
+ public static final class AppSearchManager.SetSchemaRequest {
+ }
+
+ public static final class AppSearchManager.SetSchemaRequest.Builder {
+ ctor public AppSearchManager.SetSchemaRequest.Builder();
+ method public androidx.appsearch.app.AppSearchManager.SetSchemaRequest.Builder addDataClass(Class<?>!...) throws androidx.appsearch.exceptions.AppSearchException;
+ method public androidx.appsearch.app.AppSearchManager.SetSchemaRequest.Builder addDataClass(java.util.Collection<java.lang.Class<?>!>) throws androidx.appsearch.exceptions.AppSearchException;
+ method public androidx.appsearch.app.AppSearchManager.SetSchemaRequest.Builder addSchema(androidx.appsearch.app.AppSearchSchema!...);
+ method public androidx.appsearch.app.AppSearchManager.SetSchemaRequest.Builder addSchema(java.util.Collection<androidx.appsearch.app.AppSearchSchema!>);
+ method public androidx.appsearch.app.AppSearchManager.SetSchemaRequest build();
+ method public androidx.appsearch.app.AppSearchManager.SetSchemaRequest.Builder setForceOverride(boolean);
+ }
+
public final class AppSearchResult<ValueType> {
method public String? getErrorMessage();
method public int getResultCode();
diff --git a/appsearch/appsearch/api/restricted_current.txt b/appsearch/appsearch/api/restricted_current.txt
index 0e7f3c8..1c5f6a9 100644
--- a/appsearch/appsearch/api/restricted_current.txt
+++ b/appsearch/appsearch/api/restricted_current.txt
@@ -38,6 +38,9 @@
}
public class AppSearchManager {
+ method public com.google.common.util.concurrent.ListenableFuture<androidx.appsearch.app.AppSearchBatchResult<java.lang.String!,androidx.appsearch.app.GenericDocument!>!> getDocuments(androidx.appsearch.app.AppSearchManager.GetDocumentsRequest);
+ method public com.google.common.util.concurrent.ListenableFuture<androidx.appsearch.app.AppSearchBatchResult<java.lang.String!,java.lang.Void!>!> putDocuments(androidx.appsearch.app.AppSearchManager.PutDocumentsRequest);
+ method public com.google.common.util.concurrent.ListenableFuture<androidx.appsearch.app.AppSearchResult<java.lang.Void!>!> setSchema(androidx.appsearch.app.AppSearchManager.SetSchemaRequest);
field public static final String DEFAULT_DATABASE_NAME = "";
}
@@ -47,6 +50,42 @@
method public androidx.appsearch.app.AppSearchManager.Builder setDatabaseName(String);
}
+ public static final class AppSearchManager.GetDocumentsRequest {
+ }
+
+ public static final class AppSearchManager.GetDocumentsRequest.Builder {
+ ctor public AppSearchManager.GetDocumentsRequest.Builder();
+ method public androidx.appsearch.app.AppSearchManager.GetDocumentsRequest.Builder addUris(java.lang.String!...);
+ method public androidx.appsearch.app.AppSearchManager.GetDocumentsRequest.Builder addUris(java.util.Collection<java.lang.String!>);
+ method public androidx.appsearch.app.AppSearchManager.GetDocumentsRequest build();
+ method public androidx.appsearch.app.AppSearchManager.GetDocumentsRequest.Builder setNamespace(String);
+ }
+
+ public static final class AppSearchManager.PutDocumentsRequest {
+ }
+
+ public static final class AppSearchManager.PutDocumentsRequest.Builder {
+ ctor public AppSearchManager.PutDocumentsRequest.Builder();
+ method public androidx.appsearch.app.AppSearchManager.PutDocumentsRequest.Builder addDataClass(java.lang.Object!...) throws androidx.appsearch.exceptions.AppSearchException;
+ method public androidx.appsearch.app.AppSearchManager.PutDocumentsRequest.Builder addDataClass(java.util.Collection<java.lang.Object!>) throws androidx.appsearch.exceptions.AppSearchException;
+ method public androidx.appsearch.app.AppSearchManager.PutDocumentsRequest.Builder addGenericDocument(androidx.appsearch.app.GenericDocument!...);
+ method public androidx.appsearch.app.AppSearchManager.PutDocumentsRequest.Builder addGenericDocument(java.util.Collection<androidx.appsearch.app.GenericDocument!>);
+ method public androidx.appsearch.app.AppSearchManager.PutDocumentsRequest build();
+ }
+
+ public static final class AppSearchManager.SetSchemaRequest {
+ }
+
+ public static final class AppSearchManager.SetSchemaRequest.Builder {
+ ctor public AppSearchManager.SetSchemaRequest.Builder();
+ method public androidx.appsearch.app.AppSearchManager.SetSchemaRequest.Builder addDataClass(Class<?>!...) throws androidx.appsearch.exceptions.AppSearchException;
+ method public androidx.appsearch.app.AppSearchManager.SetSchemaRequest.Builder addDataClass(java.util.Collection<java.lang.Class<?>!>) throws androidx.appsearch.exceptions.AppSearchException;
+ method public androidx.appsearch.app.AppSearchManager.SetSchemaRequest.Builder addSchema(androidx.appsearch.app.AppSearchSchema!...);
+ method public androidx.appsearch.app.AppSearchManager.SetSchemaRequest.Builder addSchema(java.util.Collection<androidx.appsearch.app.AppSearchSchema!>);
+ method public androidx.appsearch.app.AppSearchManager.SetSchemaRequest build();
+ method public androidx.appsearch.app.AppSearchManager.SetSchemaRequest.Builder setForceOverride(boolean);
+ }
+
public final class AppSearchResult<ValueType> {
method public String? getErrorMessage();
method public int getResultCode();
diff --git a/appsearch/appsearch/build.gradle b/appsearch/appsearch/build.gradle
index cf2cf0c..db965f0 100644
--- a/appsearch/appsearch/build.gradle
+++ b/appsearch/appsearch/build.gradle
@@ -30,38 +30,11 @@
sourceCompatibility = JavaVersion.VERSION_1_8
targetCompatibility = JavaVersion.VERSION_1_8
}
- buildTypes.all {
- // TODO(b/161836669): Minifiation is currently disabled because if it is on, the aar gets
- // desugared twice in some workflows, which fails. Decide how to handle this (e.g.
- // splitting targets, continuing with the consumerProguardFiles solution here, fixing the
- // flows that perform duplicate desugaring, or something else).
- minifyEnabled false
- consumerProguardFiles 'proguard-rules.pro'
- }
- // TODO(b/161205849): We've had to move libicing.so compilation into appsearch:appsearch to get
- // it included into the exported aar. Find a proper solution for bundling libicing.so into
- // appsearch-release.aar and move compilation of libicing.so back into the external/icing tree.
- sourceSets {
- androidTest.java.srcDir '../../../../external/icing/java/tests/instrumentation/'
- }
- defaultConfig {
- externalNativeBuild {
- cmake {
- cppFlags "-std=c++17"
- arguments "-DCMAKE_VERBOSE_MAKEFILE=ON"
- targets "icing"
- }
- }
- }
- externalNativeBuild {
- cmake {
- version '3.10.2'
- path '../../../../external/icing/CMakeLists.txt'
- }
- }
}
// Add :icing as jarjar dependency
+// TODO(b/163453135): this code should be ported to use bundles instead of protos, and this
+// dependency removed. It should exist ONLY inside appsearch:local-backend.
android.libraryVariants.all { variant ->
def variantName = variant.name
def suffix = variantName.capitalize()
@@ -77,11 +50,11 @@
dependencies {
api('androidx.annotation:annotation:1.1.0')
- implementation('androidx.collection:collection:1.1.0')
implementation('androidx.concurrent:concurrent-futures:1.0.0')
implementation('androidx.core:core:1.2.0')
androidTestAnnotationProcessor project(':appsearch:appsearch-compiler')
+ androidTestImplementation project(':appsearch:appsearch-local-backend')
androidTestImplementation(ANDROIDX_TEST_CORE)
androidTestImplementation(ANDROIDX_TEST_RULES)
androidTestImplementation(TRUTH)
diff --git a/appsearch/appsearch/src/androidTest/AndroidManifest.xml b/appsearch/appsearch/src/androidTest/AndroidManifest.xml
index 0988e05..563502a 100644
--- a/appsearch/appsearch/src/androidTest/AndroidManifest.xml
+++ b/appsearch/appsearch/src/androidTest/AndroidManifest.xml
@@ -15,5 +15,5 @@
-->
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
- package="androidx.appsearch.tests">
-</manifest>
\ No newline at end of file
+ package="androidx.appsearch.test">
+</manifest>
diff --git a/appsearch/appsearch/src/androidTest/java/androidx/appsearch/app/AppSearchManagerTest.java b/appsearch/appsearch/src/androidTest/java/androidx/appsearch/app/AppSearchManagerTest.java
index 5766c6d..b2b84ce 100644
--- a/appsearch/appsearch/src/androidTest/java/androidx/appsearch/app/AppSearchManagerTest.java
+++ b/appsearch/appsearch/src/androidTest/java/androidx/appsearch/app/AppSearchManagerTest.java
@@ -29,7 +29,7 @@
import androidx.appsearch.app.AppSearchSchema.PropertyConfig;
import androidx.appsearch.app.customer.EmailDataClass;
-import androidx.appsearch.impl.LocalBackend;
+import androidx.appsearch.localbackend.LocalBackend;
import androidx.test.core.app.ApplicationProvider;
import junit.framework.AssertionFailedError;
diff --git a/appsearch/appsearch/src/main/java/androidx/appsearch/app/AppSearchManager.java b/appsearch/appsearch/src/main/java/androidx/appsearch/app/AppSearchManager.java
index a861c08..6b835c0 100644
--- a/appsearch/appsearch/src/main/java/androidx/appsearch/app/AppSearchManager.java
+++ b/appsearch/appsearch/src/main/java/androidx/appsearch/app/AppSearchManager.java
@@ -140,7 +140,6 @@
* Encapsulates a request to update the schema of an {@link AppSearchManager} database.
*
* @see AppSearchManager#setSchema
- * @hide
*/
public static final class SetSchemaRequest {
private final Set<AppSearchSchema> mSchemas;
@@ -191,6 +190,8 @@
*
* @param dataClasses non-inner classes annotated with
* {@link androidx.appsearch.annotation.AppSearchDocument}.
+ * @throws AppSearchException if {@link androidx.appsearch.compiler.AppSearchCompiler}
+ * has not generated a schema for the given data classes.
*/
@NonNull
public Builder addDataClass(@NonNull Class<?>... dataClasses)
@@ -204,6 +205,8 @@
*
* @param dataClasses non-inner classes annotated with
* {@link androidx.appsearch.annotation.AppSearchDocument}.
+ * @throws AppSearchException if {@link androidx.appsearch.compiler.AppSearchCompiler}
+ * has not generated a schema for the given data classes.
*/
@NonNull
public Builder addDataClass(@NonNull Collection<Class<?>> dataClasses)
@@ -291,7 +294,6 @@
*
* @param request The schema update request.
* @return The pending result of performing this operation.
- * @hide
*/
@NonNull
public ListenableFuture<AppSearchResult<Void>> setSchema(@NonNull SetSchemaRequest request) {
@@ -303,7 +305,6 @@
* Encapsulates a request to index a document into an {@link AppSearchManager} database.
*
* @see AppSearchManager#putDocuments
- * @hide
*/
public static final class PutDocumentsRequest {
private final List<GenericDocument> mDocuments;
@@ -345,6 +346,8 @@
*
* @param dataClasses non-inner classes annotated with
* {@link androidx.appsearch.annotation.AppSearchDocument}.
+ * @throws AppSearchException if an error occurs converting a data class into a
+ * {@link GenericDocument}.
*/
@NonNull
public Builder addDataClass(@NonNull Object... dataClasses) throws AppSearchException {
@@ -357,6 +360,8 @@
*
* @param dataClasses non-inner classes annotated with
* {@link androidx.appsearch.annotation.AppSearchDocument}.
+ * @throws AppSearchException if an error occurs converting a data class into a
+ * {@link GenericDocument}.
*/
@NonNull
public Builder addDataClass(@NonNull Collection<Object> dataClasses)
@@ -400,7 +405,6 @@
* {@link AppSearchBatchResult} are the URIs of the input documents. The values are
* {@code null} if they were successfully indexed, or a failed {@link AppSearchResult}
* otherwise.
- * @hide
*/
@NonNull
public ListenableFuture<AppSearchBatchResult<String, Void>> putDocuments(
@@ -415,7 +419,6 @@
* Encapsulates a request to retrieve documents by namespace and URI.
*
* @see AppSearchManager#getDocuments
- * @hide
*/
public static final class GetDocumentsRequest {
private final String mNamespace;
@@ -494,7 +497,6 @@
* {@link GenericDocument}s on success, or a failed {@link AppSearchResult} otherwise.
* URIs that are not found will return a failed {@link AppSearchResult} with a result code
* of {@link AppSearchResult#RESULT_NOT_FOUND}.
- * @hide
*/
@NonNull
public ListenableFuture<AppSearchBatchResult<String, GenericDocument>> getDocuments(
diff --git a/appsearch/local-backend/api/current.txt b/appsearch/local-backend/api/current.txt
new file mode 100644
index 0000000..da4f6cc
--- /dev/null
+++ b/appsearch/local-backend/api/current.txt
@@ -0,0 +1 @@
+// Signature format: 3.0
diff --git a/appsearch/local-backend/api/public_plus_experimental_current.txt b/appsearch/local-backend/api/public_plus_experimental_current.txt
new file mode 100644
index 0000000..da4f6cc
--- /dev/null
+++ b/appsearch/local-backend/api/public_plus_experimental_current.txt
@@ -0,0 +1 @@
+// Signature format: 3.0
diff --git a/appsearch/local-backend/api/res-current.txt b/appsearch/local-backend/api/res-current.txt
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/appsearch/local-backend/api/res-current.txt
diff --git a/appsearch/local-backend/api/restricted_current.txt b/appsearch/local-backend/api/restricted_current.txt
new file mode 100644
index 0000000..da4f6cc
--- /dev/null
+++ b/appsearch/local-backend/api/restricted_current.txt
@@ -0,0 +1 @@
+// Signature format: 3.0
diff --git a/appsearch/local-backend/build.gradle b/appsearch/local-backend/build.gradle
new file mode 100644
index 0000000..bc8a372
--- /dev/null
+++ b/appsearch/local-backend/build.gradle
@@ -0,0 +1,99 @@
+/*
+ * 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.
+ */
+
+import androidx.build.LibraryGroups
+import androidx.build.LibraryVersions
+import androidx.build.Publish
+
+import static androidx.build.dependencies.DependenciesKt.*
+
+plugins {
+ id('AndroidXPlugin')
+ id('com.android.library')
+}
+
+android {
+ compileOptions {
+ sourceCompatibility = JavaVersion.VERSION_1_8
+ targetCompatibility = JavaVersion.VERSION_1_8
+ }
+ buildTypes.all {
+ // TODO(b/161836669): Minifiation is currently disabled because if it is on, the aar gets
+ // desugared twice in some workflows, which fails. Decide how to handle this (e.g.
+ // splitting targets, continuing with the consumerProguardFiles solution here, fixing the
+ // flows that perform duplicate desugaring, or something else).
+ minifyEnabled false
+ consumerProguardFiles 'proguard-rules.pro'
+ }
+ // TODO(b/161205849): We've had to move libicing.so compilation into appsearch:appsearch to get
+ // it included into the exported aar. Find a proper solution for bundling libicing.so into
+ // appsearch-release.aar and move compilation of libicing.so back into the external/icing tree.
+ sourceSets {
+ androidTest.java.srcDir '../../../../external/icing/java/tests/instrumentation/'
+ }
+ defaultConfig {
+ externalNativeBuild {
+ cmake {
+ cppFlags "-std=c++17"
+ arguments "-DCMAKE_VERBOSE_MAKEFILE=ON"
+ targets "icing"
+ }
+ }
+ }
+ externalNativeBuild {
+ cmake {
+ version '3.10.2'
+ path '../../../../external/icing/CMakeLists.txt'
+ }
+ }
+}
+
+// Add :icing as jarjar dependency
+android.libraryVariants.all { variant ->
+ def variantName = variant.name
+ def suffix = variantName.capitalize()
+ def jarjarConfigName = "jarjar${suffix}"
+ def jarjarConf = configurations.register(jarjarConfigName)
+ // Treat icing-java as a local jar and package it inside the aar.
+ dependencies.add(jarjarConfigName, project.dependencies.project(
+ path: ":icing",
+ configuration: jarjarConfigName))
+ dependencies.add("${variantName}Implementation", files(jarjarConf))
+}
+
+dependencies {
+ api('androidx.annotation:annotation:1.1.0')
+
+ implementation project(':appsearch:appsearch')
+ implementation('androidx.core:core:1.2.0')
+
+ androidTestImplementation(ANDROIDX_TEST_CORE)
+ androidTestImplementation(ANDROIDX_TEST_RULES)
+ androidTestImplementation(TRUTH)
+ //TODO(b/149787478) upgrade and link to Dependencies.kt
+ androidTestImplementation("junit:junit:4.13")
+}
+
+androidx {
+ name = 'AppSearch Local Backend'
+ publish = Publish.SNAPSHOT_AND_RELEASE
+ mavenGroup = LibraryGroups.APPSEARCH
+ mavenVersion = LibraryVersions.APPSEARCH
+ inceptionYear = '2020'
+ description =
+ 'A backend for appsearch which uses local app storage and a local copy of the search '
+ 'library'
+}
diff --git a/appsearch/appsearch/proguard-rules.pro b/appsearch/local-backend/proguard-rules.pro
similarity index 100%
rename from appsearch/appsearch/proguard-rules.pro
rename to appsearch/local-backend/proguard-rules.pro
diff --git a/appsearch/local-backend/src/androidTest/AndroidManifest.xml b/appsearch/local-backend/src/androidTest/AndroidManifest.xml
new file mode 100644
index 0000000..8cd95ec
--- /dev/null
+++ b/appsearch/local-backend/src/androidTest/AndroidManifest.xml
@@ -0,0 +1,19 @@
+<!--
+ 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.appsearch.localbackend.test">
+</manifest>
diff --git a/appsearch/appsearch/src/androidTest/java/androidx/appsearch/impl/AppSearchImplTest.java b/appsearch/local-backend/src/androidTest/java/androidx/appsearch/localbackend/AppSearchImplTest.java
similarity index 99%
rename from appsearch/appsearch/src/androidTest/java/androidx/appsearch/impl/AppSearchImplTest.java
rename to appsearch/local-backend/src/androidTest/java/androidx/appsearch/localbackend/AppSearchImplTest.java
index 79c83e7..b400c41 100644
--- a/appsearch/appsearch/src/androidTest/java/androidx/appsearch/impl/AppSearchImplTest.java
+++ b/appsearch/local-backend/src/androidTest/java/androidx/appsearch/localbackend/AppSearchImplTest.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package androidx.appsearch.impl;
+package androidx.appsearch.localbackend;
import static com.google.common.truth.Truth.assertThat;
diff --git a/appsearch/local-backend/src/main/AndroidManifest.xml b/appsearch/local-backend/src/main/AndroidManifest.xml
new file mode 100644
index 0000000..8a702ab
--- /dev/null
+++ b/appsearch/local-backend/src/main/AndroidManifest.xml
@@ -0,0 +1,17 @@
+<?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.
+-->
+
+<manifest package="androidx.appsearch.localbackend"/>
diff --git a/appsearch/appsearch/src/main/java/androidx/appsearch/app/GenericDocumentToProtoConverter.java b/appsearch/local-backend/src/main/java/androidx/appsearch/app/GenericDocumentToProtoConverter.java
similarity index 100%
rename from appsearch/appsearch/src/main/java/androidx/appsearch/app/GenericDocumentToProtoConverter.java
rename to appsearch/local-backend/src/main/java/androidx/appsearch/app/GenericDocumentToProtoConverter.java
diff --git a/appsearch/appsearch/src/main/java/androidx/appsearch/app/SchemaToProtoConverter.java b/appsearch/local-backend/src/main/java/androidx/appsearch/app/SchemaToProtoConverter.java
similarity index 100%
rename from appsearch/appsearch/src/main/java/androidx/appsearch/app/SchemaToProtoConverter.java
rename to appsearch/local-backend/src/main/java/androidx/appsearch/app/SchemaToProtoConverter.java
diff --git a/appsearch/appsearch/src/main/java/androidx/appsearch/app/SearchSpecToProtoConverter.java b/appsearch/local-backend/src/main/java/androidx/appsearch/app/SearchSpecToProtoConverter.java
similarity index 100%
rename from appsearch/appsearch/src/main/java/androidx/appsearch/app/SearchSpecToProtoConverter.java
rename to appsearch/local-backend/src/main/java/androidx/appsearch/app/SearchSpecToProtoConverter.java
diff --git a/appsearch/appsearch/src/main/java/androidx/appsearch/impl/AppSearchImpl.java b/appsearch/local-backend/src/main/java/androidx/appsearch/localbackend/AppSearchImpl.java
similarity index 99%
rename from appsearch/appsearch/src/main/java/androidx/appsearch/impl/AppSearchImpl.java
rename to appsearch/local-backend/src/main/java/androidx/appsearch/localbackend/AppSearchImpl.java
index 4ca03354..818bc8d 100644
--- a/appsearch/appsearch/src/main/java/androidx/appsearch/impl/AppSearchImpl.java
+++ b/appsearch/local-backend/src/main/java/androidx/appsearch/localbackend/AppSearchImpl.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package androidx.appsearch.impl;
+package androidx.appsearch.localbackend;
import android.content.Context;
import android.util.Log;
diff --git a/appsearch/appsearch/src/main/java/androidx/appsearch/impl/LocalBackend.java b/appsearch/local-backend/src/main/java/androidx/appsearch/localbackend/LocalBackend.java
similarity index 99%
rename from appsearch/appsearch/src/main/java/androidx/appsearch/impl/LocalBackend.java
rename to appsearch/local-backend/src/main/java/androidx/appsearch/localbackend/LocalBackend.java
index 021d75b..8d57c33 100644
--- a/appsearch/appsearch/src/main/java/androidx/appsearch/impl/LocalBackend.java
+++ b/appsearch/local-backend/src/main/java/androidx/appsearch/localbackend/LocalBackend.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package androidx.appsearch.impl;
+package androidx.appsearch.localbackend;
import static androidx.appsearch.app.AppSearchResult.newFailedResult;
diff --git a/browser/browser/api/api_lint.ignore b/browser/browser/api/api_lint.ignore
index 02e5401..fd2d361 100644
--- a/browser/browser/api/api_lint.ignore
+++ b/browser/browser/api/api_lint.ignore
@@ -63,6 +63,12 @@
Builder methods names should use setFoo() / addFoo() / clearFoo() style: method androidx.browser.trusted.TrustedWebActivityIntentBuilder.buildCustomTabsIntent()
+CallbackMethodName: androidx.browser.customtabs.CustomTabsCallback#extraCallback(String, android.os.Bundle):
+ Callback method names must follow the on<Something> style: extraCallback
+CallbackMethodName: androidx.browser.customtabs.CustomTabsCallback#extraCallbackWithResult(String, android.os.Bundle):
+ Callback method names must follow the on<Something> style: extraCallbackWithResult
+
+
ConcreteCollection: androidx.browser.browseractions.BrowserActionsIntent#openBrowserAction(android.content.Context, android.net.Uri, int, java.util.ArrayList<androidx.browser.browseractions.BrowserActionItem>, android.app.PendingIntent) parameter #3:
Parameter type is concrete collection (`java.util.ArrayList`); must be higher-level interface
ConcreteCollection: androidx.browser.browseractions.BrowserActionsIntent#parseBrowserActionItems(java.util.ArrayList<android.os.Bundle>) parameter #0:
diff --git a/buildSrc/jetpad-integration/src/main/java/androidx/build/jetpad/LibraryBuildInfoFile.java b/buildSrc/jetpad-integration/src/main/java/androidx/build/jetpad/LibraryBuildInfoFile.java
index af95d27..3921c6a 100644
--- a/buildSrc/jetpad-integration/src/main/java/androidx/build/jetpad/LibraryBuildInfoFile.java
+++ b/buildSrc/jetpad-integration/src/main/java/androidx/build/jetpad/LibraryBuildInfoFile.java
@@ -42,6 +42,8 @@
public String version;
public String path;
public String sha;
+ public String groupZipPath;
+ public String projectZipPath;
public Boolean groupIdRequiresSameVersion;
public ArrayList<Dependency> dependencies;
public ArrayList<Check> checks;
diff --git a/buildSrc/src/main/kotlin/androidx/build/AndroidXPlugin.kt b/buildSrc/src/main/kotlin/androidx/build/AndroidXPlugin.kt
index f181f97..8cdfa55 100644
--- a/buildSrc/src/main/kotlin/androidx/build/AndroidXPlugin.kt
+++ b/buildSrc/src/main/kotlin/androidx/build/AndroidXPlugin.kt
@@ -469,7 +469,7 @@
private fun ApplicationExtension<*, *, *, *, *>
.addAppApkToTestConfigGeneration(project: Project) {
- onVariantProperties {
+ onVariantProperties.withBuildType("debug") {
project.tasks.withType(GenerateTestConfigurationTask::class.java) {
it.appFolder.set(artifacts.get(ArtifactType.APK))
it.appLoader.set(artifacts.getBuiltArtifactsLoader())
diff --git a/buildSrc/src/main/kotlin/androidx/build/CreateLibraryBuildInfoFileTask.kt b/buildSrc/src/main/kotlin/androidx/build/CreateLibraryBuildInfoFileTask.kt
index 0028ae5..4a0fc6b 100644
--- a/buildSrc/src/main/kotlin/androidx/build/CreateLibraryBuildInfoFileTask.kt
+++ b/buildSrc/src/main/kotlin/androidx/build/CreateLibraryBuildInfoFileTask.kt
@@ -114,6 +114,8 @@
libraryBuildInfoFile.path = getProjectSpecificDirectory()
libraryBuildInfoFile.sha = getFrameworksSupportCommitShaAtHead()
libraryBuildInfoFile.groupIdRequiresSameVersion = requiresSameVersion()
+ libraryBuildInfoFile.groupZipPath = project.getGroupZipPath()
+ libraryBuildInfoFile.projectZipPath = project.getProjectZipPath()
val libraryDependencies = ArrayList<LibraryBuildInfoFile.Dependency>()
val checks = ArrayList<LibraryBuildInfoFile.Check>()
libraryBuildInfoFile.checks = checks
diff --git a/buildSrc/src/main/kotlin/androidx/build/LibraryVersions.kt b/buildSrc/src/main/kotlin/androidx/build/LibraryVersions.kt
index 87d083d..9f58aa4 100644
--- a/buildSrc/src/main/kotlin/androidx/build/LibraryVersions.kt
+++ b/buildSrc/src/main/kotlin/androidx/build/LibraryVersions.kt
@@ -59,7 +59,7 @@
val DYNAMICANIMATION_KTX = Version("1.0.0-alpha04")
val EMOJI = Version("1.2.0-alpha01")
val ENTERPRISE = Version("1.1.0-alpha02")
- val EXIFINTERFACE = Version("1.3.0-beta01")
+ val EXIFINTERFACE = Version("1.3.0-rc01")
val FRAGMENT = Version("1.3.0-alpha08")
val FUTURES = Version("1.2.0-alpha01")
val GRIDLAYOUT = Version("1.1.0-alpha01")
@@ -76,9 +76,9 @@
val LIFECYCLE = Version("2.3.0-alpha07")
val LIFECYCLE_EXTENSIONS = Version("2.2.0")
val LOADER = Version("1.2.0-alpha01")
- val MEDIA = Version("1.2.0-beta01")
+ val MEDIA = Version("1.2.0-rc01")
val MEDIA2 = Version("1.1.0-alpha02")
- val MEDIAROUTER = Version("1.2.0-alpha02")
+ val MEDIAROUTER = Version("1.2.0-beta01")
val NAVIGATION = Version("2.4.0-alpha01")
val PAGING = Version("3.0.0-alpha06")
val PALETTE = Version("1.1.0-alpha01")
@@ -90,7 +90,7 @@
val RECYCLERVIEW_SELECTION = Version("2.0.0-alpha01")
val REMOTECALLBACK = Version("1.0.0-alpha02")
val ROOM = Version("2.3.0-alpha03")
- val SAVEDSTATE = Version("1.1.0-alpha01")
+ val SAVEDSTATE = Version("1.1.0-beta01")
val SECURITY = Version("1.1.0-alpha02")
val SECURITY_BIOMETRIC = Version("1.0.0-alpha01")
val SECURITY_IDENTITY_CREDENTIAL = Version("1.0.0-alpha01")
@@ -101,16 +101,15 @@
val SLICE_BUILDERS_KTX = Version("1.0.0-alpha08")
val SLICE_REMOTECALLBACK = Version("1.0.0-alpha01")
val SLIDINGPANELAYOUT = Version("1.2.0-alpha01")
- val STARTUP = Version("1.0.0-alpha03")
+ val STARTUP = Version("1.0.0-beta01")
val SQLITE = Version("2.1.0-rc01")
val SQLITE_INSPECTOR = Version("2.1.0-alpha01")
val SWIPEREFRESHLAYOUT = Version("1.2.0-alpha01")
val TESTSCREENSHOT = Version("1.0.0-alpha01")
val TEXTCLASSIFIER = Version("1.0.0-alpha03")
- val TRACING = Version("1.0.0-beta01")
- val TRANSITION = Version("1.4.0-beta01")
+ val TRACING = Version("1.0.0-rc01")
+ val TRANSITION = Version("1.4.0-rc01")
val TVPROVIDER = Version("1.1.0-alpha01")
- val UI = Version("1.0.0-alpha02")
val VECTORDRAWABLE = Version("1.2.0-alpha02")
val VECTORDRAWABLE_ANIMATED = Version("1.2.0-alpha01")
val VECTORDRAWABLE_SEEKABLE = Version("1.0.0-alpha02")
diff --git a/buildSrc/src/main/kotlin/androidx/build/Release.kt b/buildSrc/src/main/kotlin/androidx/build/Release.kt
index 279a44d..1f4d605 100644
--- a/buildSrc/src/main/kotlin/androidx/build/Release.kt
+++ b/buildSrc/src/main/kotlin/androidx/build/Release.kt
@@ -98,14 +98,7 @@
task.destinationDirectory.set(distDir)
task.includeMetadata = params.includeMetadata
task.into("m2repository")
- val fileSuffix = if (mavenGroup == "") {
- "all"
- } else {
- mavenGroup
- .split(".")
- .joinToString("-")
- } + "-$buildNumber"
- task.archiveBaseName.set("$fileNamePrefix-$fileSuffix")
+ task.archiveBaseName.set(getZipName(fileNamePrefix, mavenGroup))
task.onlyIf {
// always run top diff zip as it is required by build on server task
task.setupIncludes()
@@ -144,6 +137,9 @@
const val DIFF_TASK_PREFIX = "createDiffArchive"
const val FULL_ARCHIVE_TASK_NAME = "createArchive"
const val DEFAULT_PUBLISH_CONFIG = "release"
+ const val GROUP_ZIPS_FOLDER = "per-group-zips"
+ const val PROJECT_ZIPS_FOLDER = "per-project-zips"
+ const val GROUP_ZIP_PREFIX = "gmaven"
// lazily created config action params so that we don't keep re-creating them
private var configActionParams: GMavenZipTask.ConfigAction.Params? = null
@@ -256,8 +252,8 @@
onConfigure = {
GMavenZipTask.ConfigAction(
getParams(project = project,
- distDir = File(project.getDistributionDirectory(), "per-group-zips"),
- fileNamePrefix = "gmaven",
+ distDir = File(project.getDistributionDirectory(), GROUP_ZIPS_FOLDER),
+ fileNamePrefix = GROUP_ZIP_PREFIX,
group = group
)
).execute(it)
@@ -277,8 +273,8 @@
name = taskName,
onConfigure = {
GMavenZipTask.ConfigAction(
- getParams(project, File(project.getDistributionDirectory(), "per-project-zips"),
- "${project.group}-${project.name}").copy(
+ getParams(project, File(project.getDistributionDirectory(),
+ PROJECT_ZIPS_FOLDER), project.projectZipPrefix()).copy(
includeMetadata = true
)
).execute(it)
@@ -322,3 +318,30 @@
it.capitalize()
}
}
+
+private fun Project.projectZipPrefix(): String {
+ return "${project.group}-${project.name}"
+}
+
+private fun getZipName(fileNamePrefix: String, mavenGroup: String): String {
+ val fileSuffix = if (mavenGroup == "") {
+ "all"
+ } else {
+ mavenGroup
+ .split(".")
+ .joinToString("-")
+ } + "-${getBuildId()}"
+ return "$fileNamePrefix-$fileSuffix"
+}
+
+fun Project.getProjectZipPath():
+ String {
+ return distSubdir() + Release.PROJECT_ZIPS_FOLDER + "/" +
+ getZipName(projectZipPrefix(), project.group.toString()) + ".zip"
+}
+
+fun Project.getGroupZipPath():
+ String {
+ return distSubdir() + Release.GROUP_ZIPS_FOLDER + "/" +
+ getZipName(Release.GROUP_ZIP_PREFIX, project.group.toString()) + ".zip"
+}
diff --git a/buildSrc/src/main/kotlin/androidx/build/SupportConfig.kt b/buildSrc/src/main/kotlin/androidx/build/SupportConfig.kt
index fad0a7b..dc355ce 100644
--- a/buildSrc/src/main/kotlin/androidx/build/SupportConfig.kt
+++ b/buildSrc/src/main/kotlin/androidx/build/SupportConfig.kt
@@ -57,6 +57,14 @@
return File(project.getCheckoutRoot(), "external")
}
+fun distSubdir(): String {
+ val subdir = System.getenv("DIST_SUBDIR")
+ if (subdir != null && subdir.isNotEmpty()) {
+ return subdir.substring(1) + "/"
+ }
+ return ""
+}
+
fun Project.getKeystore(): File {
return File(project.getSupportRootFolder(), "development/keystore/debug.keystore")
}
diff --git a/buildSrc/src/main/kotlin/androidx/build/dependencies/Dependencies.kt b/buildSrc/src/main/kotlin/androidx/build/dependencies/Dependencies.kt
index 4a2d1a6..554dd59 100644
--- a/buildSrc/src/main/kotlin/androidx/build/dependencies/Dependencies.kt
+++ b/buildSrc/src/main/kotlin/androidx/build/dependencies/Dependencies.kt
@@ -88,7 +88,7 @@
const val REACTIVE_STREAMS = "org.reactivestreams:reactive-streams:1.0.0"
const val RX_JAVA = "io.reactivex.rxjava2:rxjava:2.2.9"
const val RX_JAVA3 = "io.reactivex.rxjava3:rxjava:3.0.0"
-const val SKIKO_VERSION = "0.1.2"
+const val SKIKO_VERSION = "0.1.5"
const val SKIKO = "org.jetbrains.skiko:skiko-jvm:$SKIKO_VERSION"
const val SKIKO_LINUX = "org.jetbrains.skiko:skiko-jvm-runtime-linux:$SKIKO_VERSION"
const val SKIKO_MACOS = "org.jetbrains.skiko:skiko-jvm-runtime-macos:$SKIKO_VERSION"
@@ -120,6 +120,8 @@
const val PROTOBUF_COMPILER = "com.google.protobuf:protoc:3.10.0"
const val PROTOBUF_LITE = "com.google.protobuf:protobuf-javalite:3.10.0"
+const val JARJAR = "org.anarres.jarjar:jarjar-gradle:1.0.1"
+
// The following versions change depending on whether we are in the main or ui project - the
// specific versions are configured in build_dependencies.gradle as they are needed during
// buildSrc configuration. They are then set here in AndroidXPlugin when configuring the root
diff --git a/buildSrc/src/main/kotlin/androidx/build/metalava/MetalavaRunner.kt b/buildSrc/src/main/kotlin/androidx/build/metalava/MetalavaRunner.kt
index 74ebf6e..6ae1074 100644
--- a/buildSrc/src/main/kotlin/androidx/build/metalava/MetalavaRunner.kt
+++ b/buildSrc/src/main/kotlin/androidx/build/metalava/MetalavaRunner.kt
@@ -116,7 +116,6 @@
// The list of checks that are API lint warnings and are yet to be enabled
"SamShouldBeLast",
- "CallbackMethodName",
"GetterOnBuilder",
"StaticFinalBuilder",
"MissingGetterMatchingBuilder",
@@ -145,6 +144,7 @@
"UseIcu",
"NoByteOrShort",
"CommonArgsFirst",
+ "CallbackMethodName",
"HiddenSuperclass"
).joinToString()
)
diff --git a/busytown/androidx-studio-integration-tests.sh b/busytown/androidx-studio-integration-tests.sh
index 750a475..023769d 100755
--- a/busytown/androidx-studio-integration-tests.sh
+++ b/busytown/androidx-studio-integration-tests.sh
@@ -22,9 +22,9 @@
mkdir -p $M2REPO_DIR
# Copy internal and the output to prebuilts/tools/common/androidx-integration
cp -R $ANDROIDX_INTERNAL_DIR/* $M2REPO_DIR
-unzip -quo $OUT_DIR/dist/top-of-tree-m2repository-all-dist.zip -d $M2REPO_DIR/..
+unzip -quo $OUT_DIR/dist/top-of-tree-m2repository-all-*.zip -d $M2REPO_DIR/..
-$BAZEL_CMD test //tools/adt/idea/androidx-integration-tests:androidx-integration-tests_tests
+$BAZEL_CMD test //tools/adt/idea/androidx-integration-tests:intellij.android.androidx-integration-tests
echo "Completing $0 at $(date)"
diff --git a/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/Request.kt b/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/Request.kt
index dd211d8..5a109cd 100644
--- a/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/Request.kt
+++ b/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/Request.kt
@@ -231,3 +231,5 @@
operator fun <T> Request.get(key: CaptureRequest.Key<T>): T? = getUnchecked(key)
fun <T> Request.getOrDefault(key: CaptureRequest.Key<T>, default: T): T =
getUnchecked(key) ?: default
+
+fun Request.formatForLogs(): String = "Request($streams)@${Integer.toHexString(hashCode())}"
diff --git a/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/impl/CameraGraphComponent.kt b/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/impl/CameraGraphComponent.kt
index 01d346c..a1c4460 100644
--- a/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/impl/CameraGraphComponent.kt
+++ b/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/impl/CameraGraphComponent.kt
@@ -68,6 +68,14 @@
@Binds
abstract fun bindGraphProcessor(graphProcessor: GraphProcessorImpl): GraphProcessor
+ @Binds
+ abstract fun bindRequestProcessorFactory(
+ factory: StandardRequestProcessorFactory
+ ): RequestProcessor.Factory
+
+ @Binds
+ abstract fun bindGraphState(graphState: GraphStateImpl): GraphState
+
companion object {
@CameraGraphScope
@Provides
diff --git a/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/impl/CameraGraphImpl.kt b/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/impl/CameraGraphImpl.kt
index 9860d60..ba77a3c 100644
--- a/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/impl/CameraGraphImpl.kt
+++ b/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/impl/CameraGraphImpl.kt
@@ -16,33 +16,46 @@
package androidx.camera.camera2.pipe.impl
-import android.content.Context
import android.view.Surface
import androidx.camera.camera2.pipe.CameraGraph
+import androidx.camera.camera2.pipe.CameraMetadata
import androidx.camera.camera2.pipe.Stream
import androidx.camera.camera2.pipe.StreamConfig
import androidx.camera.camera2.pipe.StreamId
+import kotlinx.atomicfu.atomic
import javax.inject.Inject
+internal val cameraGraphIds = atomic(0)
+
@CameraGraphScope
class CameraGraphImpl @Inject constructor(
- private val context: Context,
- private val config: CameraGraph.Config,
+ graphConfig: CameraGraph.Config,
+ metadata: CameraMetadata,
private val graphProcessor: GraphProcessor,
- private val streamMap: StreamMap
+ private val streamMap: StreamMap,
+ private val graphState: GraphState
) : CameraGraph {
- private val debugId = Debug.debugIdsForGraph.incrementAndGet()
+ private val debugId = cameraGraphIds.incrementAndGet()
+
// Only one session can be active at a time.
private val sessionLock = TokenLockImpl(1)
+
+ init {
+ // Log out the configuration of the camera graph when it is created.
+ Debug.logConfiguration(this.toString(), metadata, graphConfig, streamMap)
+ }
+
override val streams: Map<StreamConfig, Stream>
get() = streamMap.streamConfigMap
override fun start() {
- graphProcessor.start()
+ Log.info { "Starting $this" }
+ graphState.start()
}
override fun stop() {
- graphProcessor.stop()
+ Log.info { "Stopping $this" }
+ graphState.stop()
}
override suspend fun acquireSession(): CameraGraph.Session {
@@ -60,8 +73,10 @@
}
override fun close() {
+ Log.info { "Closing $this" }
sessionLock.close()
graphProcessor.close()
+ graphState.stop()
}
override fun toString(): String = "CameraGraph-$debugId"
diff --git a/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/impl/CameraGraphSessionImpl.kt b/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/impl/CameraGraphSessionImpl.kt
index 0326dcb..42da9a9 100644
--- a/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/impl/CameraGraphSessionImpl.kt
+++ b/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/impl/CameraGraphSessionImpl.kt
@@ -18,12 +18,15 @@
import androidx.camera.camera2.pipe.CameraGraph
import androidx.camera.camera2.pipe.Request
+import kotlinx.atomicfu.atomic
+internal val cameraGraphSessionIds = atomic(0)
class CameraGraphSessionImpl(
private val token: TokenLock.Token,
private val graphProcessor: GraphProcessor
) : CameraGraph.Session {
- private val debugId = Debug.debugIdsForGraphSession.incrementAndGet()
+ private val debugId = cameraGraphSessionIds.incrementAndGet()
+
override fun submit(request: Request) {
graphProcessor.submit(request)
}
@@ -44,5 +47,6 @@
// Release the token so that a new instance of session can be created.
token.release()
}
+
override fun toString(): String = "CameraGraph.Session-$debugId"
}
\ No newline at end of file
diff --git a/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/impl/CameraMetadataCache.kt b/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/impl/CameraMetadataCache.kt
index 2218b2b..e610ec5 100644
--- a/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/impl/CameraMetadataCache.kt
+++ b/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/impl/CameraMetadataCache.kt
@@ -73,14 +73,30 @@
}
private fun createCameraMetadata(cameraId: CameraId, redacted: Boolean): CameraMetadataImpl {
- try {
- val cameraManager =
- context.getSystemService(Context.CAMERA_SERVICE) as CameraManager
- val characteristics =
- cameraManager.getCameraCharacteristics(cameraId.value)
- return CameraMetadataImpl(cameraId, redacted, characteristics, emptyMap())
- } catch (e: Throwable) {
- throw IllegalStateException("Failed to load camera characteristics for $cameraId", e)
+ val start = Metrics.monotonicNanos()
+
+ return Debug.trace("CameraCharacteristics_$cameraId") {
+ try {
+ val cameraManager =
+ context.getSystemService(Context.CAMERA_SERVICE) as CameraManager
+ val characteristics =
+ cameraManager.getCameraCharacteristics(cameraId.value)
+ val cameraMetadata =
+ CameraMetadataImpl(cameraId, redacted, characteristics, emptyMap())
+
+ Log.info {
+ val duration = Metrics.nanosToMillisDouble(Metrics.monotonicNanos() - start)
+ val redactedString = when (redacted) {
+ false -> ""
+ true -> " (redacted)"
+ }
+ "Loaded metadata for $cameraId in ${duration.formatMilliTime()}$redactedString"
+ }
+
+ return@trace cameraMetadata
+ } catch (e: Throwable) {
+ throw IllegalStateException("Failed to load metadata for $cameraId", e)
+ }
}
}
diff --git a/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/impl/CameraMetadataImpl.kt b/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/impl/CameraMetadataImpl.kt
index 48c8bba..914b160 100644
--- a/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/impl/CameraMetadataImpl.kt
+++ b/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/impl/CameraMetadataImpl.kt
@@ -134,6 +134,11 @@
private val _streamMap: Lazy<StreamConfigurationMap> =
lazy(LazyThreadSafetyMode.PUBLICATION) {
- get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP)!!
+ val start = Metrics.monotonicNanos()
+ val result = get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP)!!
+ val duration = Metrics.nanosToMillisDouble(Metrics.monotonicNanos() - start)
+ Log.info { "Loaded stream map for ($camera) in ${duration.formatMilliTime()}" }
+
+ result
}
}
\ No newline at end of file
diff --git a/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/impl/CameraPipeComponent.kt b/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/impl/CameraPipeComponent.kt
index 4a759ac..1849443 100644
--- a/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/impl/CameraPipeComponent.kt
+++ b/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/impl/CameraPipeComponent.kt
@@ -19,6 +19,7 @@
import android.content.Context
import android.hardware.camera2.CameraManager
import android.os.Handler
+import android.os.HandlerThread
import android.os.Process
import androidx.camera.camera2.pipe.CameraPipe
import androidx.camera.camera2.pipe.Cameras
@@ -93,10 +94,17 @@
}
}
val cameraDispatcher = cameraExecutor.asCoroutineDispatcher()
+ val lazyCameraHandlerThread = lazy {
+ HandlerThread("CXCP-Hndlr").also {
+ it.start()
+ }
+ }
val cameraHandlerProvider =
{
@Suppress("DEPRECATION")
- config.cameraThread?.let { Handler(it.looper) } ?: Handler()
+ config.cameraThread?.let { Handler(it.looper) } ?: Handler(
+ lazyCameraHandlerThread.value.looper
+ )
}
val ioExecutor = Executors.newFixedThreadPool(8) {
object : Thread(it) {
diff --git a/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/impl/Debug.kt b/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/impl/Debug.kt
index 284b984..cf44015 100644
--- a/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/impl/Debug.kt
+++ b/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/impl/Debug.kt
@@ -18,9 +18,15 @@
package androidx.camera.camera2.pipe.impl
-import android.os.Trace
+import android.hardware.camera2.CameraCharacteristics
+import android.hardware.camera2.CameraCharacteristics.LENS_FACING
+import android.hardware.camera2.CameraCharacteristics.REQUEST_AVAILABLE_CAPABILITIES
+import android.hardware.camera2.CameraMetadata.REQUEST_AVAILABLE_CAPABILITIES_LOGICAL_MULTI_CAMERA
+import android.hardware.camera2.CaptureRequest
import android.os.Build
-import kotlinx.atomicfu.atomic
+import android.os.Trace
+import androidx.camera.camera2.pipe.CameraGraph
+import androidx.camera.camera2.pipe.CameraMetadata
/**
* Internal debug utilities, constants, and checks.
@@ -69,10 +75,65 @@
}
}
- internal val debugIdsForGraph = atomic(0)
- internal val debugIdsForGraphSession = atomic(0)
- internal val debugIdsForCameraCallback = atomic(0)
- internal val debugIdsForVirtualCamera = atomic(0)
+ fun logConfiguration(
+ graphId: String,
+ metadata: CameraMetadata,
+ graphConfig: CameraGraph.Config,
+ streamMap: StreamMap
+ ) {
+ Log.info {
+ val lensFacing = when (metadata[LENS_FACING]) {
+ CameraCharacteristics.LENS_FACING_FRONT -> "Front"
+ CameraCharacteristics.LENS_FACING_BACK -> "Back"
+ CameraCharacteristics.LENS_FACING_EXTERNAL -> "External"
+ else -> "Unknown"
+ }
+
+ val operatingMode = when (graphConfig.operatingMode) {
+ CameraGraph.OperatingMode.HIGH_SPEED -> "High Speed"
+ CameraGraph.OperatingMode.NORMAL -> "Normal"
+ }
+
+ val capabilities = metadata[REQUEST_AVAILABLE_CAPABILITIES]
+ val cameraType = if (capabilities != null &&
+ capabilities.contains(
+ REQUEST_AVAILABLE_CAPABILITIES_LOGICAL_MULTI_CAMERA
+ )
+ ) {
+ "Logical"
+ } else {
+ "Physical"
+ }
+
+ StringBuilder().apply {
+ append("$graphId (Camera ${graphConfig.camera.value})\n")
+ append(" Facing: $lensFacing ($cameraType)\n")
+ append(" Mode: $operatingMode\n")
+ append("Streams:\n")
+ for (stream in streamMap.streamConfigMap) {
+ append(" ")
+ append(stream.value.id.toString().padEnd(12, ' '))
+ append(stream.value.size.toString().padEnd(12, ' '))
+ append(stream.value.format.name.padEnd(16, ' '))
+ append(stream.value.type.toString().padEnd(16, ' '))
+ append("\n")
+ }
+
+ if (graphConfig.defaultParameters.isEmpty()) {
+ append("Default Parameters: (None)")
+ } else {
+ append("Default Parameters:\n")
+ for (parameter in graphConfig.defaultParameters.filter {
+ it is CaptureRequest.Key<*>
+ }) {
+ append(" ")
+ append((parameter.key as CaptureRequest.Key<*>).name.padEnd(50, ' '))
+ append(parameter.value)
+ }
+ }
+ }.toString()
+ }
+ }
}
/**
diff --git a/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/impl/GraphProcessorImpl.kt b/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/impl/GraphProcessor.kt
similarity index 66%
rename from camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/impl/GraphProcessorImpl.kt
rename to camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/impl/GraphProcessor.kt
index f04d9da..19e67ff 100644
--- a/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/impl/GraphProcessorImpl.kt
+++ b/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/impl/GraphProcessor.kt
@@ -19,6 +19,8 @@
import android.hardware.camera2.CaptureRequest
import androidx.annotation.GuardedBy
import androidx.camera.camera2.pipe.Request
+import androidx.camera.camera2.pipe.formatForLogs
+import androidx.camera.camera2.pipe.impl.Log.debug
import androidx.camera.camera2.pipe.impl.Log.warn
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.launch
@@ -31,22 +33,6 @@
* instances.
*/
interface GraphProcessor {
- var requestProcessor: RequestProcessor?
-
- /**
- * This method puts the [GraphProcessor] into a started state. Starting the [GraphProcessor]
- * will cause it to attempt to submit all requests to the current [RequestProcessor] instance,
- * and any subsequent requests will be immediately submitted to the current [RequestProcessor].
- */
- fun start()
-
- /**
- * This method puts the [GraphProcessor] into a stopped state and clears the current
- * [RequestProcessor] instance. While the graph processor is stopped, all requests are
- * buffered.
- */
- fun stop()
-
fun setRepeating(request: Request)
fun submit(request: Request)
fun submit(requests: List<Request>)
@@ -62,6 +48,10 @@
* [GraphProcessor] is closed will be immediately aborted.
*/
fun close()
+
+ fun attach(requestProcessor: RequestProcessor)
+ fun detach(requestProcessor: RequestProcessor)
+ fun retry()
}
/**
@@ -96,106 +86,65 @@
@GuardedBy("lock")
private var closed = false
- @GuardedBy("lock")
- private var active = false
-
- override var requestProcessor: RequestProcessor?
- get() = synchronized(lock) {
- _requestProcessor
- }
- set(value) {
- val processorToClose: RequestProcessor?
- val processorToDisconnect: RequestProcessor?
- synchronized(lock) {
- processorToDisconnect = _requestProcessor
- if (closed) {
- processorToClose = value
- } else {
- processorToClose = null
- _requestProcessor = value
- }
- }
-
- if (value === processorToDisconnect) {
- warn { "RequestProcessor was set more than once." }
- return
- }
-
- // Setting the request processor to null will disconnect the old processor.
- if (processorToDisconnect != null) {
- synchronized(processorToDisconnect) {
- processorToDisconnect.disconnect()
- }
- }
-
- if (processorToClose != null) {
- synchronized(processorToClose) {
- processorToClose.stop()
- }
- return
- }
-
- if (value != null) {
- graphScope.launch {
- trySetRepeating()
- submitLoop()
- }
- }
- }
-
- override fun start() {
+ override fun attach(requestProcessor: RequestProcessor) {
+ var oldRequestProcessor: RequestProcessor? = null
synchronized(lock) {
- active = true
+ if (closed) {
+ requestProcessor.close()
+ return
+ }
+
+ if (_requestProcessor != null && _requestProcessor !== requestProcessor) {
+ oldRequestProcessor = _requestProcessor
+ }
+ _requestProcessor = requestProcessor
}
- Log.debug { "Starting GraphProcessor" }
- // TODO: Start the camera and configure the capture session.
+ val processorToClose = oldRequestProcessor
+ if (processorToClose != null) {
+ synchronized(processorToClose) {
+ processorToClose.close()
+ }
+ }
+
+ resubmit()
}
- /**
- * This method puts the [GraphProcessorImpl] into a stopped state. While the graph processor is
- * in this state, all requests are buffered in the RequestQueue.
- */
- override fun stop() {
- val processor = synchronized(lock) {
- active = false
- _requestProcessor.also { _requestProcessor = null }
+ override fun detach(requestProcessor: RequestProcessor) {
+ var oldRequestProcessor: RequestProcessor? = null
+ synchronized(lock) {
+ if (closed) {
+ return
+ }
+
+ if (requestProcessor === _requestProcessor) {
+ oldRequestProcessor = _requestProcessor
+ _requestProcessor = null
+ } else {
+ warn {
+ "Refusing to detach $requestProcessor. " +
+ "It is different from $_requestProcessor"
+ }
+ }
}
- Log.debug { "Stopping GraphProcessor" }
-
- if (processor == null) {
- return
+ val processorToClose = oldRequestProcessor
+ if (processorToClose != null) {
+ synchronized(processorToClose) {
+ processorToClose.close()
+ }
}
+ }
- // There are about ~3 main ways a Camera2 CameraCaptureSession can be shut down and closed,
- // and the behavior will be different depending on the circumstances.
- //
- // A session can be replaced by another session by simply calling createCaptureSession on
- // the CameraDevice. Internally this will reconfigure the camera capture session, and there
- // are optimizations present in the CameraFramework and Camera HAL that can optimize how
- // fast the new session is created and started. The most obvious example of this is
- // replacing a surface with a new one after recording a video, which can effectively cause
- // the new session to be created and replaced without dropping a frame.
- //
- // Second, a session can be _stopped_ by calling stopRepeating and/or abortCaptures. This
- // keeps the session alive but may abort pending requests. In some cases it's faster to
- // switch sessions if these methods are invoked before creating a new session on the
- // device because requests that are in-flight can be explicitly aborted.
- //
- // Finally, a session may be closed as a result of the underlying CameraDevice being closed
- // or disconnected. This can happen if a higher priority process steals the camera, or
- // during switches from one camera to another.
-
- graphScope.launch {
- processor.stop()
- }
+ override fun retry() {
+ resubmit()
}
override fun setRepeating(request: Request) {
synchronized(lock) {
if (closed) return
nextRepeatingRequest = request
+ debug { "Set repeating request to ${request.formatForLogs()}" }
}
graphScope.launch {
@@ -261,7 +210,7 @@
// Start with requests that have already been submitted
if (processor != null) {
synchronized(processor) {
- processor.abort()
+ processor.abortCaptures()
}
}
@@ -273,15 +222,25 @@
}
override fun close() {
+ val processor: RequestProcessor?
synchronized(lock) {
if (closed) {
return
}
closed = true
+ processor = _requestProcessor
+ _requestProcessor = null
}
+ processor?.close()
abort()
- stop()
+ }
+
+ private fun resubmit() {
+ graphScope.launch {
+ trySetRepeating()
+ submitLoop()
+ }
}
private fun read3AState(): Map<CaptureRequest.Key<*>, Any> {
@@ -310,27 +269,29 @@
val request: Request?
synchronized(lock) {
- if (closed || !active) return
+ if (closed) return
processor = _requestProcessor
request = nextRepeatingRequest ?: currentRepeatingRequest
}
if (processor != null && request != null) {
+
val extras: Map<CaptureRequest.Key<*>, Any> = read3AState()
synchronized(processor) {
if (processor.setRepeating(request, extras, requireSurfacesForAllStreams = true)) {
-
// ONLY update the current repeating request if the update succeeds
synchronized(lock) {
- currentRepeatingRequest = request
+ if (processor === _requestProcessor) {
+ currentRepeatingRequest = request
- // There is a race condition where the nextRepeating request might be changed
- // while trying to update the current repeating request. If this happens, do no
- // overwrite the pending request.
- if (nextRepeatingRequest == request) {
- nextRepeatingRequest = null
+ // There is a race condition where the nextRepeating request might be changed
+ // while trying to update the current repeating request. If this happens, do no
+ // overwrite the pending request.
+ if (nextRepeatingRequest == request) {
+ nextRepeatingRequest = null
+ }
}
}
}
@@ -343,7 +304,7 @@
var processor: RequestProcessor
synchronized(lock) {
- if (closed || !active) return
+ if (closed) return
if (submitting) {
dirty = true
diff --git a/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/impl/GraphState.kt b/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/impl/GraphState.kt
new file mode 100644
index 0000000..39cbc10
--- /dev/null
+++ b/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/impl/GraphState.kt
@@ -0,0 +1,133 @@
+/*
+ * Copyright 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.camera.camera2.pipe.impl
+
+import androidx.camera.camera2.pipe.CameraGraph
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.flow.collect
+import kotlinx.coroutines.launch
+import javax.inject.Inject
+
+interface GraphState {
+ fun start()
+ fun stop()
+ fun reconfigure()
+}
+
+/**
+ * This represents the core state loop for a Camera Graph instance.
+ *
+ * A camera graph will receive start / stop signals from the application. When started, it will do
+ * everything possible to bring up and maintain an active camera instance with the given
+ * configuration.
+ *
+ * TODO: Reorganize these constructor parameters.
+ */
+@CameraGraphScope
+class GraphStateImpl @Inject constructor(
+ @ForCameraGraph private val scope: CoroutineScope,
+ private val config: CameraGraph.Config,
+ private val graphProcessor: GraphProcessor,
+ private val sessionFactory: SessionFactory,
+ private val requestProcessorFactory: RequestProcessor.Factory,
+ private val virtualCameraManager: VirtualCameraManager,
+ private val streamMap: StreamMap
+) : GraphState {
+ private var currentCamera: VirtualCamera? = null
+ private var currentSession: VirtualSessionState? = null
+
+ override fun start() {
+ val camera = virtualCameraManager.open(
+ config.camera,
+ config.flags.allowMultipleActiveCameras
+ )
+ synchronized(this) {
+ check(currentCamera == null)
+ check(currentSession == null)
+
+ currentCamera = camera
+ currentSession = VirtualSessionState(
+ graphProcessor,
+ sessionFactory,
+ requestProcessorFactory,
+ scope
+ )
+ }
+ scope.launch { configure() }
+ }
+
+ override fun stop() {
+ val camera: VirtualCamera?
+ val session: VirtualSessionState?
+ synchronized(this) {
+ camera = currentCamera
+ session = currentSession
+
+ currentCamera = null
+ currentSession = null
+ }
+
+ scope.launch {
+ session?.disconnect()
+ camera?.disconnect()
+ }
+ }
+
+ override fun reconfigure() {
+ val oldSession: VirtualSessionState?
+ val newSession: VirtualSessionState?
+
+ synchronized(this) {
+ check(currentCamera != null) { "Cannot invoke reconfigure while stopped." }
+
+ oldSession = currentSession
+ newSession = VirtualSessionState(
+ graphProcessor,
+ sessionFactory,
+ requestProcessorFactory,
+ scope
+ )
+ currentSession = newSession
+ }
+
+ scope.launch {
+ oldSession?.disconnect()
+ configure()
+ }
+ }
+
+ private suspend fun configure() {
+ val camera: VirtualCamera?
+ val session: VirtualSessionState?
+
+ synchronized(this) {
+ camera = currentCamera
+ session = currentSession
+ }
+
+ if (camera != null && session != null) {
+ streamMap.listener = session
+ camera.state.collect {
+ if (it is CameraStateOpen) {
+ session.cameraDevice = it.cameraDevice
+ } else if (it is CameraStateClosing || it is CameraStateClosed) {
+ session.disconnect()
+ }
+ }
+ }
+ }
+}
diff --git a/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/impl/Log.kt b/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/impl/Log.kt
index 63ebae7..532138a 100644
--- a/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/impl/Log.kt
+++ b/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/impl/Log.kt
@@ -36,12 +36,12 @@
private const val LOG_LEVEL_ERROR = 4
// This indicates the lowest log level that will always log.
- private const val LOG_LEVEL = LOG_LEVEL_INFO
+ private const val LOG_LEVEL = LOG_LEVEL_DEBUG
- val DEBUG_LOGGABLE = Log.isLoggable(TAG, Log.DEBUG) || LOG_LEVEL <= LOG_LEVEL_DEBUG
- val INFO_LOGGABLE = Log.isLoggable(TAG, Log.INFO) || LOG_LEVEL <= LOG_LEVEL_INFO
- val WARN_LOGGABLE = Log.isLoggable(TAG, Log.WARN) || LOG_LEVEL <= LOG_LEVEL_WARN
- val ERROR_LOGGABLE = Log.isLoggable(TAG, Log.ERROR) || LOG_LEVEL <= LOG_LEVEL_ERROR
+ val DEBUG_LOGGABLE = LOG_LEVEL <= LOG_LEVEL_DEBUG || Log.isLoggable(TAG, Log.DEBUG)
+ val INFO_LOGGABLE = LOG_LEVEL <= LOG_LEVEL_INFO || Log.isLoggable(TAG, Log.INFO)
+ val WARN_LOGGABLE = LOG_LEVEL <= LOG_LEVEL_WARN || Log.isLoggable(TAG, Log.WARN)
+ val ERROR_LOGGABLE = LOG_LEVEL <= LOG_LEVEL_ERROR || Log.isLoggable(TAG, Log.ERROR)
/**
* Debug functions log noisy information related to the internals of the system.
diff --git a/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/impl/RequestProcessor.kt b/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/impl/RequestProcessor.kt
index 0e896d0..d8a2a22 100644
--- a/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/impl/RequestProcessor.kt
+++ b/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/impl/RequestProcessor.kt
@@ -36,12 +36,12 @@
import androidx.camera.camera2.pipe.SequenceNumber
import androidx.camera.camera2.pipe.StreamId
import androidx.camera.camera2.pipe.wrapper.CameraCaptureSessionWrapper
-import androidx.camera.camera2.pipe.wrapper.CameraDeviceWrapper
import androidx.camera.camera2.pipe.wrapper.ObjectUnavailableException
import androidx.camera.camera2.pipe.writeParameters
import kotlinx.atomicfu.atomic
import java.util.Collections.singletonList
import java.util.Collections.singletonMap
+import javax.inject.Inject
/**
* An instance of a RequestProcessor exists for the duration of a CameraCaptureSession and must be
@@ -124,44 +124,57 @@
/**
* Abort requests that have been submitted but not completed.
*/
- fun abort()
+ fun abortCaptures()
/**
- * Puts the RequestProcessor into a closed state where it will reject all incoming requests, but
- * does not actively stop repeating requests or abort pending captures.
+ * Stops the current repeating request.
*/
- fun disconnect()
+ fun stopRepeating()
/**
- * Puts the RequestProcessor into a closed state where it will reject all incoming requests and
- * then actively stops the current repeating request.
+ * Puts the RequestProcessor into a closed state where it will reject all incoming requests.
*/
- fun stop()
+ fun close()
+
+ interface Factory {
+ fun create(
+ session: CameraCaptureSessionWrapper,
+ surfaceMap: Map<StreamId, Surface>
+ ): RequestProcessor
+ }
}
-/** An interface for an object that defines which surface to use for a specific stream */
-interface SurfaceMap {
- operator fun get(streamId: StreamId): Surface?
+class StandardRequestProcessorFactory @Inject constructor(
+ private val threads: Threads,
+ private val graphConfig: CameraGraph.Config,
+ @ForCameraGraph private val graphListeners: ArrayList<Request.Listener>
+) : RequestProcessor.Factory {
+ override fun create(
+ session: CameraCaptureSessionWrapper,
+ surfaceMap: Map<StreamId, Surface>
+ ): RequestProcessor =
+ StandardRequestProcessor(session, threads, graphConfig, surfaceMap, graphListeners)
}
+internal val requestProcessorDebugIds = atomic(0)
+internal val requestSequenceDebugIds = atomic(0L)
+internal val requestTags = atomic(0L)
+internal fun nextRequestTag(): RequestNumber = RequestNumber(requestTags.incrementAndGet())
+
/**
* This class is designed to synchronously handle interactions with the Camera CaptureSession.
*/
class StandardRequestProcessor(
- private val device: CameraDeviceWrapper,
private val session: CameraCaptureSessionWrapper,
private val threads: Threads,
private val graphConfig: CameraGraph.Config,
- private val surfaceMap: SurfaceMap,
+ private val surfaceMap: Map<StreamId, Surface>,
private val graphListeners: List<Request.Listener>
) : RequestProcessor {
- private val inFlightRequests = mutableListOf<CaptureSequence>()
- private val closed = atomic(false)
- companion object {
- internal val requestTags = atomic(0L)
- fun nextRequestTag(): RequestNumber = RequestNumber(requestTags.incrementAndGet())
- }
+ private val inFlightRequests = mutableListOf<CaptureSequence>()
+ private val debugId = requestProcessorDebugIds.incrementAndGet()
+ private val closed = atomic(false)
override fun submit(
request: Request,
@@ -202,29 +215,27 @@
)
}
- override fun abort() {
+ override fun abortCaptures() {
for (sequence in inFlightRequests) {
sequence.invokeOnAborted()
}
+ session.abortCaptures()
}
- override fun disconnect() {
+ override fun stopRepeating() {
+ session.stopRepeating()
+ }
+
+ override fun close() {
closed.compareAndSet(expect = false, update = true)
}
- override fun stop() {
- if (closed.compareAndSet(expect = false, update = true)) {
- session.stopRepeating()
- }
- }
-
private fun configureAndCapture(
requests: List<Request>,
extras: Map<CaptureRequest.Key<*>, Any>,
requireStreams: Boolean,
isRepeating: Boolean
): Boolean {
-
// Reject incoming requests if this instance has been stopped or closed.
if (closed.value) {
return false
@@ -239,6 +250,8 @@
for (request in requests) {
val requestTemplate = request.template ?: graphConfig.template
+ Log.debug { "Building CaptureRequest for $request" }
+
// Check to see if there is at least one valid surface for each stream.
var hasSurface = false
for (stream in request.streams) {
@@ -249,12 +262,15 @@
val surface = surfaceMap[stream]
if (surface != null) {
+ Log.debug { " Binding $surface to $stream" }
+
// TODO(codelogic) There should be a more efficient way to do these lookups than
// having two maps.
surfaceToStreamMap[surface] = stream
streamToSurfaceMap[stream] = surface
hasSurface = true
} else if (requireStreams) {
+ Log.info { " Failed to bind surface to $stream" }
// If requireStreams is set we are required to map every stream to a valid
// Surface object for this request. If this condition is violated, then we
// return false because we cannot submit these request(s) until there is a valid
@@ -274,7 +290,7 @@
// request was not submitted.
val requestBuilder: CaptureRequest.Builder
try {
- requestBuilder = device.createCaptureRequest(requestTemplate)
+ requestBuilder = session.device.createCaptureRequest(requestTemplate)
} catch (exception: ObjectUnavailableException) {
return false
}
@@ -334,7 +350,7 @@
surfaceToStreamMap,
streamToSurfaceMap,
inFlightRequests,
- device.cameraId
+ session.device.cameraId
)
// Non-repeating requests must always be aware of abort calls.
@@ -344,8 +360,11 @@
var captured = false
return try {
+
+ Log.debug { "Submitting $captureSequence" }
capture(captureRequests, captureSequence, isRepeating)
captured = true
+ Log.debug { "Submitted $captureSequence" }
true
} catch (closedException: ObjectUnavailableException) {
false
@@ -401,6 +420,10 @@
// Invoke callbacks without holding a lock.
captureSequence.invokeOnRequestSequenceSubmitted()
}
+
+ override fun toString(): String {
+ return "RequestProcessor-$debugId"
+ }
}
/**
@@ -453,6 +476,8 @@
private val inFlightRequests: MutableList<CaptureSequence>,
private val camera: CameraId
) : CameraCaptureSession.CaptureCallback() {
+ private val debugId = requestSequenceDebugIds.incrementAndGet()
+
@Volatile
private var hasSequenceId = false
@@ -645,7 +670,7 @@
}
private fun readRequestNumber(request: CaptureRequest): RequestNumber =
- RequestNumber(checkNotNull(request.tag) as Long)
+ checkNotNull(request.tag as RequestNumber)
private fun readRequest(requestNumber: RequestNumber): RequestInfo {
if (!hasSequenceId) {
@@ -704,4 +729,6 @@
fn(request.request.listeners[i])
}
}
+
+ override fun toString(): String = "CaptureSequence-$debugId"
}
diff --git a/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/impl/SessionFactory.kt b/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/impl/SessionFactory.kt
index ab90bf3..09459db 100644
--- a/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/impl/SessionFactory.kt
+++ b/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/impl/SessionFactory.kt
@@ -274,10 +274,10 @@
val outputs = arrayListOf<OutputConfigurationWrapper>()
val deferredOutputs = mutableMapOf<StreamId, OutputConfigurationWrapper>()
- for (stream in graphConfig.streams) {
- val streamId = streamMap.streamConfigMap[stream]!!.id
- val physicalCameraId = if (stream.camera != graphConfig.camera) {
- stream.camera
+ for (streamConfig in graphConfig.streams) {
+ val streamId = streamMap.streamConfigMap[streamConfig]!!.id
+ val physicalCameraId = if (streamConfig.camera != graphConfig.camera) {
+ streamConfig.camera
} else {
null
}
@@ -286,8 +286,8 @@
val config = AndroidOutputConfiguration.create(
surface,
- streamType = stream.type,
- size = stream.size,
+ streamType = streamConfig.type,
+ size = streamConfig.size,
physicalCameraId = physicalCameraId
)
diff --git a/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/impl/StreamMap.kt b/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/impl/StreamMap.kt
index 2a48683..94e67db 100644
--- a/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/impl/StreamMap.kt
+++ b/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/impl/StreamMap.kt
@@ -89,18 +89,24 @@
streamConfigMap = streamBuilder
}
- private var _virtualSession: VirtualSessionState? = null
- var virtualSession: VirtualSessionState?
- get() = _virtualSession
+ private var _listener: SurfaceListener? = null
+ var listener: SurfaceListener?
+ get() = _listener
set(value) {
- _virtualSession = value
+ _listener = value
if (value != null) {
maybeUpdateSurfaces()
}
}
operator fun set(stream: StreamId, surface: Surface?) {
- Log.info { "Configured $stream to use $surface" }
+ Log.info {
+ if (surface != null) {
+ "Configured $stream to use $surface"
+ } else {
+ "Removed surface for $stream"
+ }
+ }
if (surface == null) {
// TODO: Tell the graph processor that it should resubmit the repeating request or
// reconfigure the camera2 captureSession
@@ -112,7 +118,7 @@
}
private fun maybeUpdateSurfaces() {
- val session = _virtualSession ?: return
+ val surfaceListener = _listener ?: return
// Rules:
// 1. In order to tell the captureSession that we have surfaces, we should wait until we
@@ -137,7 +143,7 @@
return
}
- session.surfaceMap = surfaces
+ surfaceListener.setSurfaceMap(surfaces)
}
// Using an inline class generates a synthetic constructor
diff --git a/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/impl/VirtualCamera.kt b/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/impl/VirtualCamera.kt
index 6de8bd1..baa41dd 100644
--- a/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/impl/VirtualCamera.kt
+++ b/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/impl/VirtualCamera.kt
@@ -25,6 +25,7 @@
import androidx.camera.camera2.pipe.wrapper.AndroidCameraDevice
import androidx.camera.camera2.pipe.wrapper.CameraDeviceWrapper
import androidx.camera.camera2.pipe.wrapper.closeWithTrace
+import kotlinx.atomicfu.atomic
import kotlinx.coroutines.Job
import kotlinx.coroutines.coroutineScope
import kotlinx.coroutines.flow.Flow
@@ -94,10 +95,12 @@
fun disconnect()
}
+internal val virtualCameraDebugIds = atomic(0)
+
class VirtualCameraState(
val cameraId: CameraId
) : VirtualCamera {
- private val debugId = Debug.debugIdsForVirtualCamera.incrementAndGet()
+ private val debugId = virtualCameraDebugIds.incrementAndGet()
private val lock = Any()
@GuardedBy("lock")
@@ -153,13 +156,15 @@
override fun toString(): String = "VirtualCamera-$debugId"
}
+internal val androidCameraDebugIds = atomic(0)
+
internal class AndroidCameraState(
val cameraId: CameraId,
val metadata: CameraMetadata,
private val attemptNumber: Int,
private val attemptTimestampNanos: Long
) : CameraDevice.StateCallback() {
- private val debugId = Debug.debugIdsForCameraCallback.incrementAndGet()
+ private val debugId = androidCameraDebugIds.incrementAndGet()
private val lock = Any()
@GuardedBy("lock")
@@ -176,7 +181,7 @@
get() = _state
init {
- Log.debug { "$cameraId: Opening" }
+ Log.info { "Opening $cameraId" }
requestTimestampNanos =
if (attemptNumber == 1) {
attemptTimestampNanos
@@ -193,8 +198,6 @@
null
}
- Log.info { "About to close $device" }
-
closeWith(
device?.unwrap(),
ClosingInfo(ClosedReason.APP_CLOSED)
@@ -209,14 +212,15 @@
check(cameraDevice.id == cameraId.value)
val openedTimestamp = Metrics.monotonicNanos()
openTimestampNanos = openedTimestamp
- val attemptDuration = Metrics.nanosToMillis(openedTimestamp - requestTimestampNanos)
- val totalDuration = Metrics.nanosToMillis(openedTimestamp - attemptTimestampNanos)
- Log.debug {
+
+ Log.info {
+ val attemptDuration = openedTimestamp - requestTimestampNanos
+ val totalDuration = openedTimestamp - attemptTimestampNanos
if (attemptNumber == 1) {
- "$cameraId: onOpened after ${attemptDuration}ms"
+ "Opened $cameraId in ${attemptDuration.formatNanoTime()}"
} else {
- "$cameraId: onOpened after ${attemptDuration}ms " +
- "(${totalDuration}ms total) and $attemptNumber attempts."
+ "Opened $cameraId in ${attemptDuration.formatNanoTime()} " +
+ "(${totalDuration.formatNanoTime()} total) after $attemptNumber attempts."
}
}
diff --git a/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/impl/VirtualCameraManager.kt b/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/impl/VirtualCameraManager.kt
index dd53121..c69a306 100644
--- a/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/impl/VirtualCameraManager.kt
+++ b/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/impl/VirtualCameraManager.kt
@@ -48,6 +48,8 @@
internal object RequestCloseAll : CameraRequest()
+private const val requestQueueDepth = 8
+
@Suppress("EXPERIMENTAL_API_USAGE")
@Singleton
class VirtualCameraManager @Inject constructor(
@@ -56,7 +58,7 @@
private val permissions: Permissions,
private val threads: Threads
) {
- private val requestQueue: Channel<CameraRequest> = Channel(8)
+ private val requestQueue: Channel<CameraRequest> = Channel(requestQueueDepth)
private val activeCameras: MutableSet<ActiveCamera> = mutableSetOf()
init {
@@ -74,7 +76,9 @@
}
private fun offerChecked(request: CameraRequest) {
- check(requestQueue.offer(request)) { " There are more than 8 requests buffered!" }
+ check(requestQueue.offer(request)) {
+ "There are more than $requestQueueDepth requests buffered!"
+ }
}
private suspend fun requestLoop() = coroutineScope {
@@ -89,16 +93,13 @@
val closeRequest = requests.firstOrNull { it is RequestClose } as? RequestClose
if (closeRequest != null) {
requests.remove(closeRequest)
- Log.info { "CloseRequest: $closeRequest" }
if (activeCameras.contains(closeRequest.activeCamera)) {
activeCameras.remove(closeRequest.activeCamera)
}
launch {
- Log.info { "Closing active camera" }
closeRequest.activeCamera.close()
}
- Log.info { "Waiting for active camera to close" }
closeRequest.activeCamera.awaitClosed()
continue
}
@@ -347,7 +348,6 @@
scope,
timeout = 1000,
callback = {
- Log.debug { "Wakelock expired." }
channel.offer(RequestClose(this))
})
@@ -355,7 +355,6 @@
listenerJob = scope.launch {
androidCameraState.state.collect {
if (it is CameraStateClosing || it is CameraStateClosed) {
- Log.debug { " Camera is in $it state, releasing wakelock" }
wakelock.release()
this.cancel()
}
diff --git a/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/impl/VirtualSessionState.kt b/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/impl/VirtualSessionState.kt
index 1bd1cff..e9ab5aa 100644
--- a/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/impl/VirtualSessionState.kt
+++ b/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/impl/VirtualSessionState.kt
@@ -17,14 +17,106 @@
package androidx.camera.camera2.pipe.impl
import android.view.Surface
+
+import androidx.annotation.GuardedBy
import androidx.camera.camera2.pipe.StreamId
import androidx.camera.camera2.pipe.wrapper.CameraCaptureSessionWrapper
+import androidx.camera.camera2.pipe.wrapper.CameraDeviceWrapper
+import androidx.camera.camera2.pipe.wrapper.OutputConfigurationWrapper
import kotlinx.atomicfu.atomic
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.launch
+import java.util.Collections.synchronizedMap
+
+interface SurfaceListener {
+ fun setSurfaceMap(surfaces: Map<StreamId, Surface>)
+}
internal val virtualSessionDebugIds = atomic(0)
-class VirtualSessionState : CameraCaptureSessionWrapper.StateCallback, SurfaceMap {
+
+/**
+ * This class encapsulates the state and logic required to create and start a CaptureSession.
+ *
+ * After being created, it will wait for a valid CameraDevice and Surfaces that it will use
+ * to create and start the capture session. Calling shutdown or disconnect will release the current
+ * session (if one has been configured), and prevent / close any session that was in the process of
+ * being created when shutdown / disconnect was called.
+ */
+class VirtualSessionState(
+ private val graphProcessor: GraphProcessor,
+ private val sessionFactory: SessionFactory,
+ private val requestProcessorFactory: RequestProcessor.Factory,
+ private val scope: CoroutineScope
+) : CameraCaptureSessionWrapper.StateCallback, SurfaceListener {
private val debugId = virtualSessionDebugIds.incrementAndGet()
- var surfaceMap: Map<StreamId, Surface>? = null
+ private val lock = Any()
+
+ private val activeSurfaceMap = synchronizedMap(HashMap<StreamId, Surface>())
+
+ private var sessionCreatingTimestamp: Long? = null
+
+ @GuardedBy("lock")
+ private var _cameraDevice: CameraDeviceWrapper? = null
+ var cameraDevice: CameraDeviceWrapper?
+ get() = synchronized(lock) { _cameraDevice }
+ set(value) = synchronized(lock) {
+ if (state == State.CLOSING || state == State.CLOSED) {
+ return
+ }
+
+ _cameraDevice = value
+ if (value != null) {
+ scope.launch { tryCreateCaptureSession() }
+ }
+ }
+
+ @GuardedBy("lock")
+ private var cameraCaptureSession: ConfiguredCameraCaptureSession? = null
+
+ @GuardedBy("lock")
+ private var pendingOutputMap: Map<StreamId, OutputConfigurationWrapper>? = null
+
+ @GuardedBy("lock")
+ private var pendingSurfaceMap: Map<StreamId, Surface>? = null
+
+ @GuardedBy("lock")
+ private var state = State.PENDING
+
+ private enum class State {
+ PENDING,
+ CREATING,
+ CREATED,
+ CLOSING,
+ CLOSED
+ }
+
+ @GuardedBy("lock")
+ private var _surfaceMap: Map<StreamId, Surface>? = null
+ override fun setSurfaceMap(surfaces: Map<StreamId, Surface>) {
+ synchronized(lock) {
+ if (state == State.CLOSING || state == State.CLOSED) {
+ return@synchronized
+ }
+
+ _surfaceMap = surfaces
+
+ val pendingOutputs = pendingOutputMap
+ if (pendingOutputs != null && pendingSurfaceMap == null) {
+
+ // Filter the list of current surfaces down ones that are present in the set of
+ // deferred outputs.
+ val pendingSurfaces = surfaces.filter { pendingOutputs.containsKey(it.key) }
+
+ // We can only invoke finishDeferredOutputs after we have a surface for ALL
+ // of the deferred outputs.
+ if (pendingSurfaces.size == pendingOutputs.size) {
+ pendingSurfaceMap = pendingSurfaces
+ scope.launch { finalizeOutputsIfAvailable() }
+ }
+ }
+ scope.launch { tryCreateCaptureSession() }
+ }
+ }
override fun onActive(session: CameraCaptureSessionWrapper) {
Log.debug { "$this Active" }
@@ -32,14 +124,17 @@
override fun onClosed(session: CameraCaptureSessionWrapper) {
Log.debug { "$this Closed" }
+ shutdown()
}
override fun onConfigureFailed(session: CameraCaptureSessionWrapper) {
- Log.debug { "$this ConfigureFailed" }
+ Log.warn { "Failed to configure $this" }
+ shutdown()
}
override fun onConfigured(session: CameraCaptureSessionWrapper) {
- Log.info { "$this Configured" }
+ Log.debug { "$this Configured" }
+ configure(session)
}
override fun onReady(session: CameraCaptureSessionWrapper) {
@@ -50,9 +145,53 @@
Log.debug { "$this Active" }
}
- /** Return a Surface that should be used for a specific stream */
- override fun get(streamId: StreamId): Surface? {
- TODO("Implemented in a future change.")
+ private fun configure(session: CameraCaptureSessionWrapper?) {
+ val captureSession: ConfiguredCameraCaptureSession?
+ var tryConfigureDeferred = false
+
+ // This block is designed to do two things:
+ // 1. Get or create a RequestProcessor instance.
+ // 2. Pass the requestProcessor to the graphProcessor after the session is fully created and
+ // the onConfigured callback has been invoked.
+ synchronized(lock) {
+ if (state == State.CLOSING || state == State.CLOSED) {
+ return
+ }
+
+ if (cameraCaptureSession == null && session != null) {
+ captureSession = ConfiguredCameraCaptureSession(
+ session,
+ requestProcessorFactory.create(session, activeSurfaceMap)
+ )
+ cameraCaptureSession = captureSession
+ } else {
+ captureSession = cameraCaptureSession
+ }
+
+ if (state != State.CREATED || captureSession == null) {
+ return
+ }
+
+ // Finalize deferredConfigs if finalizeOutputConfigurations was previously invoked.
+ if (pendingOutputMap != null && pendingSurfaceMap != null) {
+ tryConfigureDeferred = true
+ }
+ }
+
+ if (tryConfigureDeferred) {
+ finalizeOutputsIfAvailable(retryAllowed = false)
+ }
+
+ synchronized(lock) {
+ captureSession?.let {
+ Log.info {
+ val duration = Metrics.monotonicNanos() - sessionCreatingTimestamp!!
+ "Configured $this in ${duration.formatNanoTime()}"
+ }
+
+ graphProcessor.attach(it.processor)
+ }
+ }
}
/**
@@ -60,14 +199,162 @@
* a closed state. This will not cancel repeating requests or abort captures.
*/
fun disconnect() {
+ val captureSession = synchronized(lock) {
+ if (state == State.CLOSING || state == State.CLOSED) {
+ return@synchronized null
+ }
+
+ cameraCaptureSession.also {
+ cameraCaptureSession = null
+ state = State.CLOSING
+ }
+ }
+
+ if (captureSession != null) {
+ graphProcessor.detach(captureSession.processor)
+ }
+
+ synchronized(this) {
+ _cameraDevice = null
+ state = State.CLOSED
+ }
}
/**
* This is used to disconnect the cached [CameraCaptureSessionWrapper] and put this object into
* a closed state. This may stop the repeating request and abort captures.
*/
- fun shutdown() {
+ private fun shutdown() {
+ val captureSession = synchronized(lock) {
+ if (state == State.CLOSING || state == State.CLOSED) {
+ return@synchronized null
+ }
+
+ cameraCaptureSession.also {
+ cameraCaptureSession = null
+ state = State.CLOSING
+ }
+ }
+
+ if (captureSession != null) {
+ graphProcessor.detach(captureSession.processor)
+ captureSession.processor.stopRepeating()
+ captureSession.processor.abortCaptures()
+ }
+
+ synchronized(this) {
+ _cameraDevice = null
+ state = State.CLOSED
+ }
+ }
+
+ private fun finalizeOutputsIfAvailable(retryAllowed: Boolean = true) {
+ val captureSession: ConfiguredCameraCaptureSession?
+ val pendingOutputs: Map<StreamId, OutputConfigurationWrapper>?
+ val pendingSurfaces: Map<StreamId, Surface>?
+ synchronized(lock) {
+ captureSession = cameraCaptureSession
+ pendingOutputs = pendingOutputMap
+ pendingSurfaces = pendingSurfaceMap
+ }
+
+ if (captureSession != null && pendingOutputs != null && pendingSurfaces != null) {
+ val finalizedStartTime = Metrics.monotonicNanos()
+ for ((streamId, outputConfig) in pendingOutputs) {
+ // TODO: Consider adding support for experimental libraries on older devices.
+
+ val surface = checkNotNull(pendingSurfaces[streamId])
+ outputConfig.addSurface(surface)
+ }
+
+ // It's possible that more than one stream maps to the same output configuration since
+ // output configurations support multiple surfaces. If this happens, we may have more
+ // deferred outputs than outputConfiguration objects.
+ val distinctOutputs = pendingOutputs.mapTo(mutableSetOf()) { it.value }.toList()
+ captureSession.session.finalizeOutputConfigurations(distinctOutputs)
+
+ var tryResubmit = false
+ synchronized(lock) {
+ if (state == State.CREATED) {
+ activeSurfaceMap.putAll(pendingSurfaces)
+ Log.info {
+ val finalizationTime = Metrics.monotonicNanos() - finalizedStartTime
+ "Finalized ${pendingOutputs.map { it.key }} for $this in " +
+ finalizationTime.formatNanoTime()
+ }
+ tryResubmit = true
+ }
+ }
+
+ if (tryResubmit && retryAllowed) {
+ graphProcessor.retry()
+ }
+ }
+ }
+
+ private fun tryCreateCaptureSession() {
+ val surfaces: Map<StreamId, Surface>?
+ val device: CameraDeviceWrapper?
+ synchronized(lock) {
+ if (state != State.PENDING) {
+ return
+ }
+
+ surfaces = _surfaceMap
+ device = _cameraDevice
+ if (surfaces == null || device == null) {
+ return
+ }
+
+ state = State.CREATING
+ sessionCreatingTimestamp = Metrics.monotonicNanos()
+ }
+
+ // Create the capture session and return a Map of StreamId -> OutputConfiguration for any
+ // outputs that were not initially available. These will be configured later.
+ Log.info {
+ "Creating CameraCaptureSession from ${device?.cameraId} using $this with $surfaces"
+ }
+ val deferred = sessionFactory.create(device!!, surfaces!!, this)
+
+ synchronized(lock) {
+ if (state == State.CLOSING || state == State.CLOSED) {
+ Log.info { "Warning: $this was $state while configuration was in progress." }
+ return
+ }
+ check(state == State.CREATING) { "Unexpected state: $state" }
+ state = State.CREATED
+
+ activeSurfaceMap.putAll(surfaces)
+ if (deferred.isNotEmpty()) {
+ Log.info {
+ "Created $this with ${surfaces.keys.toList()}. " +
+ "Waiting to finalize ${deferred.keys.toList()}"
+ }
+ pendingOutputMap = deferred
+
+ val availableDeferredSurfaces = _surfaceMap?.filter {
+ deferred.containsKey(it.key)
+ }
+
+ if (availableDeferredSurfaces != null &&
+ availableDeferredSurfaces.size == deferred.size
+ ) {
+ pendingSurfaceMap = availableDeferredSurfaces
+ }
+ }
+ }
+
+ // There are rare cases where the onConfigured call may be invoked synchronously. If this
+ // happens, we need to invoke configure here to make sure the session ends up in a valid
+ // state.
+ configure(session = null)
}
override fun toString(): String = "VirtualSessionState-$debugId"
+
+ private data class ConfiguredCameraCaptureSession(
+ val session: CameraCaptureSessionWrapper,
+ val processor: RequestProcessor
+ )
}
\ No newline at end of file
diff --git a/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/wrapper/CameraDevice.kt b/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/wrapper/CameraDevice.kt
index d06319e..4fe7cb9 100644
--- a/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/wrapper/CameraDevice.kt
+++ b/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/wrapper/CameraDevice.kt
@@ -32,7 +32,7 @@
import androidx.camera.camera2.pipe.impl.Debug
import androidx.camera.camera2.pipe.impl.Log
import androidx.camera.camera2.pipe.impl.Metrics
-import androidx.camera.camera2.pipe.impl.formatMilliTime
+import androidx.camera.camera2.pipe.impl.formatNanoTime
import androidx.camera.camera2.pipe.writeParameter
/** Interface around a [CameraDevice] with minor modifications.
@@ -113,11 +113,11 @@
this?.let {
val start = Metrics.monotonicNanos()
Log.info { "Closing Camera ${it.id}" }
- Debug.trace("Camera ${it.id}#close") {
+ Debug.trace("$it#close") {
it.close()
}
- val duration = Metrics.nanosToMillisDouble(Metrics.monotonicNanos() - start)
- Log.info { "Closed Camera ${it.id} in ${duration.formatMilliTime()}" }
+ val duration = Metrics.monotonicNanos() - start
+ Log.info { "Closed Camera ${it.id} in ${duration.formatNanoTime()}" }
}
}
diff --git a/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/wrapper/CaptureSession.kt b/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/wrapper/CaptureSession.kt
index 5ccc813..b5a9a42 100644
--- a/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/wrapper/CaptureSession.kt
+++ b/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/wrapper/CaptureSession.kt
@@ -25,6 +25,7 @@
import androidx.annotation.RequiresApi
import androidx.camera.camera2.pipe.UnsafeWrapper
import androidx.camera.camera2.pipe.impl.Log
+import kotlinx.atomicfu.atomic
import java.io.Closeable
/**
@@ -176,38 +177,43 @@
private val device: CameraDeviceWrapper,
private val stateCallback: CameraCaptureSessionWrapper.StateCallback
) : CameraCaptureSession.StateCallback() {
- private var captureSession: CameraCaptureSessionWrapper? = null
+ private val captureSession = atomic<CameraCaptureSessionWrapper?>(null)
override fun onConfigured(session: CameraCaptureSession) {
- check(captureSession == null) {
- "captureSession was configured multiple times. Old: $captureSession, New: $session"
- }
- captureSession = wrapSession(session)
- stateCallback.onConfigured(checkNotNull(captureSession))
+ stateCallback.onConfigured(getWrapped(session))
}
override fun onConfigureFailed(session: CameraCaptureSession) {
- check(captureSession == null) {
- "captureSession was configured multiple times. Old: $captureSession, New: $session"
- }
- captureSession = wrapSession(session)
- stateCallback.onConfigureFailed(checkNotNull(captureSession))
+ stateCallback.onConfigureFailed(getWrapped(session))
}
override fun onReady(session: CameraCaptureSession) {
- stateCallback.onReady(checkNotNull(captureSession))
+ stateCallback.onReady(getWrapped(session))
}
override fun onActive(session: CameraCaptureSession) {
- stateCallback.onActive(checkNotNull(captureSession))
+ stateCallback.onActive(getWrapped(session))
}
override fun onClosed(session: CameraCaptureSession) {
- stateCallback.onClosed(checkNotNull(captureSession))
+ stateCallback.onClosed(getWrapped(session))
}
override fun onCaptureQueueEmpty(session: CameraCaptureSession) {
- stateCallback.onCaptureQueueEmpty(checkNotNull(captureSession))
+ stateCallback.onCaptureQueueEmpty(getWrapped(session))
+ }
+
+ private fun getWrapped(session: CameraCaptureSession): CameraCaptureSessionWrapper {
+ var local = captureSession.value
+ if (local != null) {
+ return local
+ }
+
+ local = wrapSession(session)
+ if (captureSession.compareAndSet(null, local)) {
+ return local
+ }
+ return captureSession.value!!
}
private fun wrapSession(session: CameraCaptureSession): CameraCaptureSessionWrapper {
diff --git a/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/wrapper/Configuration.kt b/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/wrapper/Configuration.kt
index d9a5d7e..0761aeb 100644
--- a/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/wrapper/Configuration.kt
+++ b/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/wrapper/Configuration.kt
@@ -139,34 +139,31 @@
// Create the OutputConfiguration using the groupId via the constructor (if set)
val configuration: OutputConfiguration
- if (streamType == StreamType.SURFACE) {
- check(surface != null) {
- "Surface must not be null when creating an $streamType typed " +
- "OutputConfiguration."
- }
+ if (surface != null) {
configuration = if (surfaceGroupId != SURFACE_GROUP_ID_NONE) {
OutputConfiguration(surfaceGroupId, surface)
} else {
OutputConfiguration(surface)
}
} else {
- check(size != null) {
- "Size must not be null when creating a $streamType OutputConfiguration."
- }
-
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) {
throw IllegalStateException(
- "OutputConfiguration is not supported on API ${Build.VERSION.SDK_INT} " +
- "(requires API ${Build.VERSION_CODES.O})"
+ "Deferred OutputConfigurations are not supported on API " +
+ "${Build.VERSION.SDK_INT} (requires API ${Build.VERSION_CODES.O})"
)
}
+
+ check(size != null) {
+ "Size must defined when creating a deferred OutputConfiguration."
+ }
+
configuration = OutputConfiguration(
size,
when (streamType) {
StreamType.SURFACE_TEXTURE -> SurfaceTexture::class.java
StreamType.SURFACE_VIEW -> SurfaceHolder::class.java
StreamType.SURFACE -> throw IllegalArgumentException(
- " is not supported for deferred OutputConfigurations"
+ "StreamType.Surface is not supported for deferred OutputConfigurations"
)
}
)
diff --git a/camera/camera-camera2-pipe/src/test/java/androidx/camera/camera2/pipe/impl/CameraGraphImplTest.kt b/camera/camera-camera2-pipe/src/test/java/androidx/camera/camera2/pipe/impl/CameraGraphImplTest.kt
index 370bea2..4b2420c 100644
--- a/camera/camera-camera2-pipe/src/test/java/androidx/camera/camera2/pipe/impl/CameraGraphImplTest.kt
+++ b/camera/camera-camera2-pipe/src/test/java/androidx/camera/camera2/pipe/impl/CameraGraphImplTest.kt
@@ -16,7 +16,6 @@
package androidx.camera.camera2.pipe.impl
-import android.content.Context
import android.hardware.camera2.CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL
import android.hardware.camera2.CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL_FULL
import android.os.Build
@@ -27,7 +26,7 @@
import androidx.camera.camera2.pipe.testing.FakeCameraMetadata
import androidx.camera.camera2.pipe.testing.FakeCameras
import androidx.camera.camera2.pipe.testing.FakeGraphProcessor
-import androidx.test.core.app.ApplicationProvider
+import androidx.camera.camera2.pipe.testing.FakeGraphState
import androidx.test.filters.SmallTest
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.runBlocking
@@ -47,7 +46,8 @@
mapOf(INFO_SUPPORTED_HARDWARE_LEVEL to INFO_SUPPORTED_HARDWARE_LEVEL_FULL),
cameraId = fakeCameraId
)
- private val graphProcessor = FakeGraphProcessor()
+ private val fakeGraphProcessor = FakeGraphProcessor()
+ private val fakeGraphState = FakeGraphState()
private lateinit var impl: CameraGraphImpl
@Before
@@ -57,8 +57,16 @@
streams = listOf(),
template = RequestTemplate(0)
)
- val context = ApplicationProvider.getApplicationContext() as Context
- impl = CameraGraphImpl(context, config, graphProcessor, StreamMap(fakeMetadata, config))
+ impl = CameraGraphImpl(
+ config,
+ fakeMetadata,
+ fakeGraphProcessor,
+ StreamMap(
+ fakeMetadata,
+ config
+ ),
+ fakeGraphState
+ )
}
@Test
@@ -100,7 +108,7 @@
val request = Request(listOf())
session.submit(request)
- assertThat(graphProcessor.requestQueue).contains(listOf(request))
+ assertThat(fakeGraphProcessor.requestQueue).contains(listOf(request))
}
@Test
@@ -109,7 +117,7 @@
val request = Request(listOf())
session.setRepeating(request)
- assertThat(graphProcessor.repeatingRequest).isSameInstanceAs(request)
+ assertThat(fakeGraphProcessor.repeatingRequest).isSameInstanceAs(request)
}
@Test
@@ -119,7 +127,7 @@
session.submit(request)
session.abort()
- assertThat(graphProcessor.requestQueue).isEmpty()
+ assertThat(fakeGraphProcessor.requestQueue).isEmpty()
}
@Test
@@ -127,26 +135,26 @@
val session = impl.acquireSessionOrNull()
checkNotNull(session).close()
- assertThat(graphProcessor.closed).isFalse()
+ assertThat(fakeGraphProcessor.closed).isFalse()
}
@Test
fun closingCameraGraphClosesGraphProcessor() {
impl.close()
- assertThat(graphProcessor.closed).isTrue()
+ assertThat(fakeGraphProcessor.closed).isTrue()
}
@Test
fun stoppingCameraGraphStopsGraphProcessor() {
- assertThat(graphProcessor.active).isFalse()
+ assertThat(fakeGraphState.active).isFalse()
impl.start()
- assertThat(graphProcessor.active).isTrue()
+ assertThat(fakeGraphState.active).isTrue()
impl.stop()
- assertThat(graphProcessor.active).isFalse()
+ assertThat(fakeGraphState.active).isFalse()
impl.start()
- assertThat(graphProcessor.active).isTrue()
+ assertThat(fakeGraphState.active).isTrue()
impl.close()
- assertThat(graphProcessor.closed).isTrue()
- assertThat(graphProcessor.active).isFalse()
+ assertThat(fakeGraphProcessor.closed).isTrue()
+ assertThat(fakeGraphState.active).isFalse()
}
}
\ No newline at end of file
diff --git a/camera/camera-camera2-pipe/src/test/java/androidx/camera/camera2/pipe/impl/GraphProcessorTest.kt b/camera/camera-camera2-pipe/src/test/java/androidx/camera/camera2/pipe/impl/GraphProcessorTest.kt
index 27b4852..ab0bb19 100644
--- a/camera/camera-camera2-pipe/src/test/java/androidx/camera/camera2/pipe/impl/GraphProcessorTest.kt
+++ b/camera/camera-camera2-pipe/src/test/java/androidx/camera/camera2/pipe/impl/GraphProcessorTest.kt
@@ -58,9 +58,7 @@
this,
arrayListOf(globalListener)
)
- graphProcessor.start()
-
- graphProcessor.requestProcessor = fakeProcessor1
+ graphProcessor.attach(fakeProcessor1)
graphProcessor.submit(request1)
}
@@ -81,10 +79,9 @@
this,
arrayListOf(globalListener)
)
- graphProcessor.start()
- graphProcessor.requestProcessor = fakeProcessor1
- graphProcessor.requestProcessor = fakeProcessor2
+ graphProcessor.attach(fakeProcessor1)
+ graphProcessor.attach(fakeProcessor2)
graphProcessor.submit(request1)
}
@@ -108,14 +105,13 @@
this,
arrayListOf(globalListener)
)
- graphProcessor.start()
graphProcessor.submit(request1)
graphProcessor.submit(request2)
// Request1 and 2 should be queued and will be submitted even when the request
// processor is set after the requests are submitted.
- graphProcessor.requestProcessor = fakeProcessor1
+ graphProcessor.attach(fakeProcessor1)
}
// Make sure the requests get submitted to the request processor
@@ -135,10 +131,9 @@
this,
arrayListOf(globalListener)
)
- graphProcessor.start()
graphProcessor.submit(listOf(request1, request2))
- graphProcessor.requestProcessor = fakeProcessor1
+ graphProcessor.attach(fakeProcessor1)
}
assertThat(fakeProcessor1.requestQueue).hasSize(1)
@@ -154,10 +149,9 @@
this,
arrayListOf(globalListener)
)
- graphProcessor.start()
fakeProcessor1.rejectRequests = true
- graphProcessor.requestProcessor = fakeProcessor1
+ graphProcessor.attach(fakeProcessor1)
graphProcessor.submit(request1)
assertThat(fakeProcessor1.nextEvent().rejected).isTrue()
@@ -165,7 +159,7 @@
graphProcessor.submit(request2)
assertThat(fakeProcessor1.nextEvent().rejected).isTrue()
- graphProcessor.requestProcessor = fakeProcessor2
+ graphProcessor.attach(fakeProcessor2)
assertThat(fakeProcessor2.nextEvent().request!!.burst[0]).isSameInstanceAs(request1)
assertThat(fakeProcessor2.nextEvent().request!!.burst[0]).isSameInstanceAs(request2)
}
@@ -184,12 +178,11 @@
this,
arrayListOf(globalListener)
)
- graphProcessor.start()
// Note: setting the requestProcessor, and calling submit() can both trigger a call
// to submit a request.
fakeProcessor1.rejectRequests = true
- graphProcessor.requestProcessor = fakeProcessor1
+ graphProcessor.attach(fakeProcessor1)
graphProcessor.submit(request1)
// Check to make sure that submit is called at least once, and that request1 is rejected
@@ -201,7 +194,7 @@
// Stop rejecting requests
fakeProcessor1.rejectRequests = false
assertThat(fakeProcessor1.rejectRequests).isFalse()
- assertThat(fakeProcessor1.disconnectInvoked).isFalse()
+ assertThat(fakeProcessor1.closeInvoked).isFalse()
assertThat(fakeProcessor1.stopInvoked).isFalse()
graphProcessor.submit(request2)
@@ -227,9 +220,8 @@
this,
arrayListOf(globalListener)
)
- graphProcessor.start()
- graphProcessor.requestProcessor = fakeProcessor1
+ graphProcessor.attach(fakeProcessor1)
graphProcessor.setRepeating(request1)
graphProcessor.setRepeating(request2)
}
@@ -245,13 +237,12 @@
this,
arrayListOf(globalListener)
)
- graphProcessor.start()
- graphProcessor.requestProcessor = fakeProcessor1
+ graphProcessor.attach(fakeProcessor1)
graphProcessor.setRepeating(request1)
awaitEvent(fakeProcessor1, request1) { it.setRepeating }
- graphProcessor.requestProcessor = fakeProcessor2
+ graphProcessor.attach(fakeProcessor2)
awaitEvent(fakeProcessor2, request1) { it.setRepeating }
}
@@ -267,13 +258,12 @@
this,
arrayListOf(globalListener)
)
- graphProcessor.start()
fakeProcessor1.rejectRequests = true
- graphProcessor.requestProcessor = fakeProcessor1
+ graphProcessor.attach(fakeProcessor1)
graphProcessor.setRepeating(request1)
- graphProcessor.requestProcessor = fakeProcessor2
+ graphProcessor.attach(fakeProcessor2)
awaitEvent(fakeProcessor2, request1) { it.setRepeating }
}
@@ -288,12 +278,11 @@
this,
arrayListOf(globalListener)
)
- graphProcessor.start()
graphProcessor.setRepeating(request1)
graphProcessor.submit(request2)
- graphProcessor.requestProcessor = fakeProcessor1
+ graphProcessor.attach(fakeProcessor1)
}
assertThat(fakeProcessor1.repeatingRequest?.burst).contains(request1)
@@ -308,14 +297,13 @@
this,
arrayListOf(globalListener)
)
- graphProcessor.start()
graphProcessor.setRepeating(request1)
graphProcessor.submit(request2)
// Abort queued and in-flight requests.
graphProcessor.abort()
- graphProcessor.requestProcessor = fakeProcessor1
+ graphProcessor.attach(fakeProcessor1)
}
assertThat(requestListener1.lastAbortedRequest).isNull()
@@ -334,11 +322,10 @@
this,
arrayListOf(globalListener)
)
- graphProcessor.start()
graphProcessor.close()
// Abort queued and in-flight requests.
- graphProcessor.requestProcessor = fakeProcessor1
+ graphProcessor.attach(fakeProcessor1)
graphProcessor.setRepeating(request1)
graphProcessor.submit(request2)
}
@@ -347,7 +334,7 @@
assertThat(requestListener1.lastAbortedRequest).isNull()
assertThat(requestListener2.lastAbortedRequest).isSameInstanceAs(request2)
- assertThat(fakeProcessor1.stopInvoked).isTrue()
+ assertThat(fakeProcessor1.closeInvoked).isTrue()
assertThat(fakeProcessor1.repeatingRequest).isNull()
assertThat(fakeProcessor1.requestQueue).isEmpty()
}
diff --git a/camera/camera-camera2-pipe/src/test/java/androidx/camera/camera2/pipe/impl/SessionFactoryTest.kt b/camera/camera-camera2-pipe/src/test/java/androidx/camera/camera2/pipe/impl/SessionFactoryTest.kt
index 8122f0f..6eb59f3 100644
--- a/camera/camera-camera2-pipe/src/test/java/androidx/camera/camera2/pipe/impl/SessionFactoryTest.kt
+++ b/camera/camera-camera2-pipe/src/test/java/androidx/camera/camera2/pipe/impl/SessionFactoryTest.kt
@@ -24,6 +24,8 @@
import androidx.camera.camera2.pipe.CameraGraph
import androidx.camera.camera2.pipe.testing.CameraPipeRobolectricTestRunner
import androidx.camera.camera2.pipe.testing.FakeCameras
+import androidx.camera.camera2.pipe.testing.FakeGraphProcessor
+import androidx.camera.camera2.pipe.testing.FakeRequestProcessor
import androidx.test.core.app.ApplicationProvider
import androidx.test.filters.SmallTest
import com.google.common.truth.Truth.assertThat
@@ -41,7 +43,6 @@
@CameraGraphScope
@Component(
modules = [
-
FakeCameras.FakeCameraGraphModule::class
]
)
@@ -69,7 +70,7 @@
@Test
fun canCreateSessionFactoryTestComponent() = runBlockingTest {
- val component = DaggerCameraSessionTestComponent.builder()
+ val component: CameraSessionTestComponent = DaggerCameraSessionTestComponent.builder()
.fakeCameraGraphModule(
FakeCameras.FakeCameraGraphModule(context, testCamera)
)
@@ -81,7 +82,7 @@
@Test
fun createCameraCaptureSession() = runBlockingTest {
- val component = DaggerCameraSessionTestComponent.builder()
+ val component: CameraSessionTestComponent = DaggerCameraSessionTestComponent.builder()
.fakeCameraGraphModule(
FakeCameras.FakeCameraGraphModule(context, testCamera)
)
@@ -102,7 +103,12 @@
val pendingOutputs = sessionFactory.create(
testCamera.cameraDeviceWrapper,
mapOf(stream1.id to surface),
- virtualSessionState = VirtualSessionState()
+ virtualSessionState = VirtualSessionState(
+ FakeGraphProcessor(),
+ sessionFactory,
+ FakeRequestProcessor(),
+ this
+ )
)
assertThat(pendingOutputs).isNotNull()
diff --git a/camera/camera-camera2-pipe/src/test/java/androidx/camera/camera2/pipe/impl/StreamMapTest.kt b/camera/camera-camera2-pipe/src/test/java/androidx/camera/camera2/pipe/impl/StreamMapTest.kt
index ec620d1..c17292b 100644
--- a/camera/camera-camera2-pipe/src/test/java/androidx/camera/camera2/pipe/impl/StreamMapTest.kt
+++ b/camera/camera-camera2-pipe/src/test/java/androidx/camera/camera2/pipe/impl/StreamMapTest.kt
@@ -152,14 +152,14 @@
streamMap[stream2.id] = fakeSurface2
streamMap[stream3.id] = fakeSurface3
- val session = VirtualSessionState()
+ val session = FakeSurfaceListener()
- streamMap.virtualSession = session
+ streamMap.listener = session
- assertThat(session.surfaceMap).isNotNull()
- assertThat(session.surfaceMap?.get(stream1.id)).isEqualTo(fakeSurface1)
- assertThat(session.surfaceMap?.get(stream2.id)).isEqualTo(fakeSurface2)
- assertThat(session.surfaceMap?.get(stream3.id)).isEqualTo(fakeSurface3)
+ assertThat(session.surfaces).isNotNull()
+ assertThat(session.surfaces?.get(stream1.id)).isEqualTo(fakeSurface1)
+ assertThat(session.surfaces?.get(stream2.id)).isEqualTo(fakeSurface2)
+ assertThat(session.surfaces?.get(stream3.id)).isEqualTo(fakeSurface3)
}
@Test
@@ -173,20 +173,20 @@
val fakeSurface2 = Surface(SurfaceTexture(2))
val fakeSurface3 = Surface(SurfaceTexture(3))
- val session = VirtualSessionState()
- streamMap.virtualSession = session
- assertThat(session.surfaceMap).isNull()
+ val session = FakeSurfaceListener()
+ streamMap.listener = session
+ assertThat(session.surfaces).isNull()
streamMap[stream1.id] = fakeSurface1
- assertThat(session.surfaceMap).isNull()
+ assertThat(session.surfaces).isNull()
streamMap[stream2.id] = fakeSurface2
streamMap[stream3.id] = fakeSurface3
- assertThat(session.surfaceMap).isNotNull()
- assertThat(session.surfaceMap?.get(stream1.id)).isEqualTo(fakeSurface1)
- assertThat(session.surfaceMap?.get(stream2.id)).isEqualTo(fakeSurface2)
- assertThat(session.surfaceMap?.get(stream3.id)).isEqualTo(fakeSurface3)
+ assertThat(session.surfaces).isNotNull()
+ assertThat(session.surfaces?.get(stream1.id)).isEqualTo(fakeSurface1)
+ assertThat(session.surfaces?.get(stream2.id)).isEqualTo(fakeSurface2)
+ assertThat(session.surfaces?.get(stream3.id)).isEqualTo(fakeSurface3)
}
@Test
@@ -201,19 +201,19 @@
val fakeSurface3 = Surface(SurfaceTexture(3))
val fakeSurface4 = Surface(SurfaceTexture(4))
- val session = VirtualSessionState()
- streamMap.virtualSession = session
+ val session = FakeSurfaceListener()
+ streamMap.listener = session
streamMap[stream1.id] = fakeSurface1
streamMap[stream1.id] = fakeSurface2
- assertThat(session.surfaceMap).isNull()
+ assertThat(session.surfaces).isNull()
streamMap[stream2.id] = fakeSurface3
streamMap[stream3.id] = fakeSurface4
- assertThat(session.surfaceMap).isNotNull()
- assertThat(session.surfaceMap?.get(stream1.id)).isEqualTo(fakeSurface2)
- assertThat(session.surfaceMap?.get(stream2.id)).isEqualTo(fakeSurface3)
- assertThat(session.surfaceMap?.get(stream3.id)).isEqualTo(fakeSurface4)
+ assertThat(session.surfaces).isNotNull()
+ assertThat(session.surfaces?.get(stream1.id)).isEqualTo(fakeSurface2)
+ assertThat(session.surfaces?.get(stream2.id)).isEqualTo(fakeSurface3)
+ assertThat(session.surfaces?.get(stream3.id)).isEqualTo(fakeSurface4)
}
@Test
@@ -227,15 +227,15 @@
val fakeSurface2 = Surface(SurfaceTexture(2))
val fakeSurface3 = Surface(SurfaceTexture(3))
- val session = VirtualSessionState()
- streamMap.virtualSession = session
+ val session = FakeSurfaceListener()
+ streamMap.listener = session
streamMap[stream1.id] = fakeSurface1
- streamMap.virtualSession = null
+ streamMap.listener = null
streamMap[stream2.id] = fakeSurface2
streamMap[stream3.id] = fakeSurface3
- assertThat(session.surfaceMap).isNull()
+ assertThat(session.surfaces).isNull()
}
@Test
@@ -253,15 +253,23 @@
streamMap[stream2.id] = fakeSurface2
streamMap[stream3.id] = fakeSurface3
- val session1 = VirtualSessionState()
- streamMap.virtualSession = session1
+ val session1 = FakeSurfaceListener()
+ streamMap.listener = session1
- val session2 = VirtualSessionState()
- streamMap.virtualSession = session2
+ val session2 = FakeSurfaceListener()
+ streamMap.listener = session2
- assertThat(session2.surfaceMap).isNotNull()
- assertThat(session2.surfaceMap?.get(stream1.id)).isEqualTo(fakeSurface1)
- assertThat(session2.surfaceMap?.get(stream2.id)).isEqualTo(fakeSurface2)
- assertThat(session2.surfaceMap?.get(stream3.id)).isEqualTo(fakeSurface3)
+ assertThat(session2.surfaces).isNotNull()
+ assertThat(session2.surfaces?.get(stream1.id)).isEqualTo(fakeSurface1)
+ assertThat(session2.surfaces?.get(stream2.id)).isEqualTo(fakeSurface2)
+ assertThat(session2.surfaces?.get(stream3.id)).isEqualTo(fakeSurface3)
+ }
+
+ class FakeSurfaceListener : SurfaceListener {
+ var surfaces: Map<StreamId, Surface>? = null
+
+ override fun setSurfaceMap(surfaces: Map<StreamId, Surface>) {
+ this.surfaces = surfaces
+ }
}
}
\ No newline at end of file
diff --git a/camera/camera-camera2-pipe/src/test/java/androidx/camera/camera2/pipe/testing/FakeGraphProcessor.kt b/camera/camera-camera2-pipe/src/test/java/androidx/camera/camera2/pipe/testing/FakeGraphProcessor.kt
index dc648c0..919e0ef 100644
--- a/camera/camera-camera2-pipe/src/test/java/androidx/camera/camera2/pipe/testing/FakeGraphProcessor.kt
+++ b/camera/camera-camera2-pipe/src/test/java/androidx/camera/camera2/pipe/testing/FakeGraphProcessor.kt
@@ -24,7 +24,7 @@
* Fake implementation of a [GraphProcessor] for tests.
*/
class FakeGraphProcessor : GraphProcessor {
- var active = false
+ var active = true
private set
var closed = false
private set
@@ -34,18 +34,7 @@
get() = _requestQueue
private val _requestQueue = mutableListOf<List<Request>>()
-
- override var requestProcessor: RequestProcessor? = null
-
- override fun start() {
- if (!closed) {
- active = true
- }
- }
-
- override fun stop() {
- active = false
- }
+ private var processor: RequestProcessor? = null
override fun setRepeating(request: Request) {
repeatingRequest = request
@@ -71,4 +60,21 @@
active = false
_requestQueue.clear()
}
+
+ override fun attach(requestProcessor: RequestProcessor) {
+ val old = processor
+ processor = requestProcessor
+ old?.close()
+ }
+
+ override fun detach(requestProcessor: RequestProcessor) {
+ val old = processor
+ if (requestProcessor === old) {
+ processor = null
+ old.close()
+ }
+ }
+
+ override fun retry() {
+ }
}
\ No newline at end of file
diff --git a/appsearch/appsearch/src/main/java/androidx/appsearch/impl/package-info.java b/camera/camera-camera2-pipe/src/test/java/androidx/camera/camera2/pipe/testing/FakeGraphState.kt
similarity index 62%
copy from appsearch/appsearch/src/main/java/androidx/appsearch/impl/package-info.java
copy to camera/camera-camera2-pipe/src/test/java/androidx/camera/camera2/pipe/testing/FakeGraphState.kt
index 68c3b98..f97f87d 100644
--- a/appsearch/appsearch/src/main/java/androidx/appsearch/impl/package-info.java
+++ b/camera/camera-camera2-pipe/src/test/java/androidx/camera/camera2/pipe/testing/FakeGraphState.kt
@@ -14,10 +14,23 @@
* limitations under the License.
*/
-/**
- * @hide
- */
-@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
-package androidx.appsearch.impl;
+package androidx.camera.camera2.pipe.testing
-import androidx.annotation.RestrictTo;
+import androidx.camera.camera2.pipe.impl.GraphState
+
+class FakeGraphState : GraphState {
+ var active = false
+ var reconfigured = false
+
+ override fun start() {
+ active = true
+ }
+
+ override fun stop() {
+ active = false
+ }
+
+ override fun reconfigure() {
+ reconfigured = true
+ }
+}
\ No newline at end of file
diff --git a/camera/camera-camera2-pipe/src/test/java/androidx/camera/camera2/pipe/testing/FakeRequestProcessor.kt b/camera/camera-camera2-pipe/src/test/java/androidx/camera/camera2/pipe/testing/FakeRequestProcessor.kt
index 08ab06c..a0eb857 100644
--- a/camera/camera-camera2-pipe/src/test/java/androidx/camera/camera2/pipe/testing/FakeRequestProcessor.kt
+++ b/camera/camera-camera2-pipe/src/test/java/androidx/camera/camera2/pipe/testing/FakeRequestProcessor.kt
@@ -17,17 +17,20 @@
package androidx.camera.camera2.pipe.testing
import android.hardware.camera2.CaptureRequest
+import android.view.Surface
import androidx.camera.camera2.pipe.Request
+import androidx.camera.camera2.pipe.StreamId
import androidx.camera.camera2.pipe.impl.RequestProcessor
import androidx.camera.camera2.pipe.impl.TokenLock
import androidx.camera.camera2.pipe.impl.TokenLockImpl
+import androidx.camera.camera2.pipe.wrapper.CameraCaptureSessionWrapper
import kotlinx.coroutines.channels.Channel
import kotlinx.coroutines.withTimeout
/**
* Fake implementation of a [RequestProcessor] for tests.
*/
-class FakeRequestProcessor : RequestProcessor {
+class FakeRequestProcessor : RequestProcessor, RequestProcessor.Factory {
private val eventChannel = Channel<Event>(Channel.UNLIMITED)
val requestQueue: MutableList<FakeRequest> = mutableListOf()
@@ -36,11 +39,14 @@
var rejectRequests = false
var abortInvoked = false
private set
- var disconnectInvoked = false
+ var closeInvoked = false
private set
var stopInvoked = false
private set
+ var captureSession: CameraCaptureSessionWrapper? = null
+ var surfaceMap: Map<StreamId, Surface>? = null
+
private val tokenLock = TokenLockImpl(1)
private var token: TokenLock.Token? = null
@@ -50,6 +56,15 @@
val requireStreams: Boolean = false
)
+ override fun create(
+ session: CameraCaptureSessionWrapper,
+ surfaceMap: Map<StreamId, Surface>
+ ): RequestProcessor {
+ captureSession = session
+ this.surfaceMap = surfaceMap
+ return this
+ }
+
override fun submit(
request: Request,
extraRequestParameters: Map<CaptureRequest.Key<*>, Any>,
@@ -58,7 +73,7 @@
val fakeRequest =
FakeRequest(listOf(request), extraRequestParameters, requireSurfacesForAllStreams)
- if (rejectRequests || disconnectInvoked || stopInvoked) {
+ if (rejectRequests || closeInvoked) {
eventChannel.offer(Event(request = fakeRequest, rejected = true))
return false
}
@@ -76,7 +91,7 @@
): Boolean {
val fakeRequest =
FakeRequest(requests, extraRequestParameters, requireSurfacesForAllStreams)
- if (rejectRequests || disconnectInvoked || stopInvoked) {
+ if (rejectRequests || closeInvoked) {
eventChannel.offer(Event(request = fakeRequest, rejected = true))
return false
}
@@ -94,7 +109,7 @@
): Boolean {
val fakeRequest =
FakeRequest(listOf(request), extraRequestParameters, requireSurfacesForAllStreams)
- if (rejectRequests || disconnectInvoked || stopInvoked) {
+ if (rejectRequests || closeInvoked) {
eventChannel.offer(Event(request = fakeRequest, rejected = true))
return false
}
@@ -104,21 +119,21 @@
return true
}
- override fun abort() {
+ override fun abortCaptures() {
abortInvoked = true
eventChannel.offer(Event(abort = true))
}
- override fun disconnect() {
- disconnectInvoked = true
- eventChannel.offer(Event(disconnect = true))
- }
-
- override fun stop() {
+ override fun stopRepeating() {
stopInvoked = true
eventChannel.offer(Event(stop = true))
}
+ override fun close() {
+ closeInvoked = true
+ eventChannel.offer(Event(close = true))
+ }
+
/**
* Get the next event from queue with an option to specify a timeout for tests.
*/
@@ -131,7 +146,7 @@
val request: FakeRequestProcessor.FakeRequest? = null,
val rejected: Boolean = false,
val abort: Boolean = false,
- val disconnect: Boolean = false,
+ val close: Boolean = false,
val stop: Boolean = false,
val submit: Boolean = false,
val setRepeating: Boolean = false
diff --git a/camera/camera-camera2/src/androidTest/java/androidx/camera/camera2/internal/ExposureDeviceTest.java b/camera/camera-camera2/src/androidTest/java/androidx/camera/camera2/internal/ExposureDeviceTest.java
new file mode 100644
index 0000000..7b32ba50
--- /dev/null
+++ b/camera/camera-camera2/src/androidTest/java/androidx/camera/camera2/internal/ExposureDeviceTest.java
@@ -0,0 +1,538 @@
+/*
+ * Copyright 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.camera.camera2.internal;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static junit.framework.TestCase.assertTrue;
+
+import static org.junit.Assume.assumeFalse;
+import static org.junit.Assume.assumeTrue;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.timeout;
+import static org.mockito.Mockito.verify;
+
+import android.graphics.SurfaceTexture;
+import android.hardware.camera2.CameraCaptureSession;
+import android.hardware.camera2.CameraDevice;
+import android.hardware.camera2.CaptureRequest;
+import android.hardware.camera2.CaptureResult;
+import android.hardware.camera2.TotalCaptureResult;
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.util.Size;
+import android.view.Surface;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.experimental.UseExperimental;
+import androidx.camera.camera2.internal.compat.CameraManagerCompat;
+import androidx.camera.camera2.internal.util.SemaphoreReleasingCamera2Callbacks;
+import androidx.camera.camera2.interop.Camera2Interop;
+import androidx.camera.core.CameraSelector;
+import androidx.camera.core.CameraUnavailableException;
+import androidx.camera.core.ExperimentalExposureCompensation;
+import androidx.camera.core.ExposureState;
+import androidx.camera.core.ImageCapture;
+import androidx.camera.core.impl.CameraControlInternal;
+import androidx.camera.core.impl.CameraInfoInternal;
+import androidx.camera.core.impl.CameraInternal;
+import androidx.camera.core.impl.CameraStateRegistry;
+import androidx.camera.core.impl.DeferrableSurface;
+import androidx.camera.core.impl.ImmediateSurface;
+import androidx.camera.core.impl.SessionConfig;
+import androidx.camera.core.impl.utils.executor.CameraXExecutors;
+import androidx.camera.core.internal.CameraUseCaseAdapter;
+import androidx.camera.testing.CameraUtil;
+import androidx.camera.testing.fakes.FakeCameraDeviceSurfaceManager;
+import androidx.camera.testing.fakes.FakeUseCase;
+import androidx.camera.testing.fakes.FakeUseCaseConfig;
+import androidx.core.os.HandlerCompat;
+import androidx.test.core.app.ApplicationProvider;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.LargeTest;
+
+import org.junit.After;
+import org.junit.AfterClass;
+import org.junit.Before;
+import org.junit.BeforeClass;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TestRule;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Semaphore;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
+
+/**
+ * Contains tests for {@link androidx.camera.camera2.internal.ExposureControl} internal
+ * implementation.
+ */
+@LargeTest
+@RunWith(AndroidJUnit4.class)
+@UseExperimental(markerClass = ExperimentalExposureCompensation.class)
+public class ExposureDeviceTest {
+
+ @CameraSelector.LensFacing
+ private static final int DEFAULT_LENS_FACING = CameraSelector.LENS_FACING_BACK;
+ // For the purpose of this test, always say we have 1 camera available.
+ private static final int DEFAULT_AVAILABLE_CAMERA_COUNT = 1;
+
+ @Rule
+ public TestRule mUseCamera = CameraUtil.grantCameraPermissionAndPreTest();
+
+ private ArrayList<FakeTestUseCase> mFakeTestUseCases = new ArrayList<>();
+ private Camera2CameraImpl mCamera2CameraImpl;
+ private static ExecutorService sCameraExecutor;
+ private static HandlerThread sCameraHandlerThread;
+ private static Handler sCameraHandler;
+ private CameraStateRegistry mCameraStateRegistry;
+ Semaphore mSemaphore;
+ String mCameraId;
+ SemaphoreReleasingCamera2Callbacks.SessionStateCallback mSessionStateCallback;
+ private CameraUseCaseAdapter mCameraUseCaseAdapter;
+ private CameraInfoInternal mCameraInfoInternal;
+ private CameraControlInternal mCameraControlInternal;
+
+ @BeforeClass
+ public static void classSetup() {
+ sCameraHandlerThread = new HandlerThread("cameraThread");
+ sCameraHandlerThread.start();
+ sCameraHandler = HandlerCompat.createAsync(sCameraHandlerThread.getLooper());
+ sCameraExecutor = CameraXExecutors.newHandlerExecutor(sCameraHandler);
+ }
+
+ @AfterClass
+ public static void classTeardown() {
+ sCameraHandlerThread.quitSafely();
+ }
+
+ @Before
+ public void setup() throws CameraUnavailableException {
+ // TODO(b/162296654): Workaround the google_3a specific behavior.
+ assumeFalse("Cuttlefish uses google_3a v1 or v2 it might fail to set EV before "
+ + "first AE converge.", android.os.Build.MODEL.contains("Cuttlefish"));
+ assumeFalse("Pixel uses google_3a v1 or v2 it might fail to set EV before "
+ + "first AE converge.", android.os.Build.MODEL.contains("Pixel"));
+
+ assumeTrue(CameraUtil.deviceHasCamera());
+ assumeTrue(CameraUtil.hasCameraWithLensFacing(DEFAULT_LENS_FACING));
+ mSessionStateCallback = new SemaphoreReleasingCamera2Callbacks.SessionStateCallback();
+ mCameraId = CameraUtil.getCameraIdWithLensFacing(DEFAULT_LENS_FACING);
+ mSemaphore = new Semaphore(0);
+ mCameraStateRegistry = new CameraStateRegistry(DEFAULT_AVAILABLE_CAMERA_COUNT);
+ mCamera2CameraImpl = new Camera2CameraImpl(
+ CameraManagerCompat.from(ApplicationProvider.getApplicationContext()), mCameraId,
+ mCameraStateRegistry, sCameraExecutor, sCameraHandler);
+
+ mCameraInfoInternal = mCamera2CameraImpl.getCameraInfoInternal();
+ mCameraControlInternal = mCamera2CameraImpl.getCameraControlInternal();
+ mCamera2CameraImpl.open();
+
+ FakeCameraDeviceSurfaceManager fakeCameraDeviceSurfaceManager =
+ new FakeCameraDeviceSurfaceManager();
+ fakeCameraDeviceSurfaceManager.setSuggestedResolution(mCameraId, FakeUseCaseConfig.class,
+ new Size(640, 480));
+
+ mCameraUseCaseAdapter = new CameraUseCaseAdapter(mCamera2CameraImpl,
+ new LinkedHashSet<>(Collections.singleton(mCamera2CameraImpl)),
+ fakeCameraDeviceSurfaceManager);
+ }
+
+ @After
+ public void teardown() throws InterruptedException, ExecutionException {
+ // Need to release the camera no matter what is done, otherwise the CameraDevice is not
+ // closed.
+ // When the CameraDevice is not closed, then it can cause problems with interferes with
+ // other test cases.
+ if (mCameraUseCaseAdapter != null) {
+ mCameraUseCaseAdapter.removeUseCases(
+ Collections.unmodifiableCollection(mFakeTestUseCases));
+ }
+ if (mCamera2CameraImpl != null) {
+ mCamera2CameraImpl.release().get();
+ }
+
+ for (FakeTestUseCase fakeUseCase : mFakeTestUseCases) {
+ fakeUseCase.clear();
+ }
+ }
+
+ private FakeTestUseCase openUseCase() throws CameraUseCaseAdapter.CameraException {
+ FakeUseCaseConfig.Builder configBuilder =
+ new FakeUseCaseConfig.Builder().setTargetName("UseCase");
+ new Camera2Interop.Extender<>(configBuilder).setSessionStateCallback(mSessionStateCallback);
+
+ FakeTestUseCase testUseCase = new FakeTestUseCase(configBuilder.getUseCaseConfig(),
+ mCamera2CameraImpl, mSessionStateCallback);
+ mFakeTestUseCases.add(testUseCase);
+
+ mCameraUseCaseAdapter.addUseCases(Collections.singletonList(testUseCase));
+ mCameraUseCaseAdapter.attachUseCases();
+
+ return testUseCase;
+ }
+
+ @Test
+ public void setExposure_futureResultTest() throws InterruptedException, TimeoutException,
+ ExecutionException, CameraUseCaseAdapter.CameraException {
+ ExposureState exposureState = mCameraInfoInternal.getExposureState();
+ assumeTrue(exposureState.isExposureCompensationSupported());
+ int upper = exposureState.getExposureCompensationRange().getUpper();
+
+ openUseCase();
+ // Wait a little bit for the camera to open.
+ assertTrue(mSessionStateCallback.waitForOnConfigured(1));
+
+ int ret = mCameraControlInternal.setExposureCompensationIndex(upper).get(3000,
+ TimeUnit.MILLISECONDS);
+ assertThat(ret).isEqualTo(upper);
+ }
+
+ @Test
+ public void setExposureTest() throws InterruptedException, TimeoutException,
+ ExecutionException, CameraUseCaseAdapter.CameraException {
+ ExposureState exposureState = mCameraInfoInternal.getExposureState();
+ assumeTrue(exposureState.isExposureCompensationSupported());
+ int upper = exposureState.getExposureCompensationRange().getUpper();
+
+ FakeTestUseCase useCase = openUseCase();
+ // Wait a little bit for the camera to open.
+ assertTrue(mSessionStateCallback.waitForOnConfigured(1));
+
+ // Set the exposure compensation
+ mCameraControlInternal.setExposureCompensationIndex(upper).get(3000, TimeUnit.MILLISECONDS);
+
+ ArgumentCaptor<TotalCaptureResult> captureResultCaptor = ArgumentCaptor.forClass(
+ TotalCaptureResult.class);
+ CameraCaptureSession.CaptureCallback callback = mock(
+ CameraCaptureSession.CaptureCallback.class);
+ useCase.setCameraCaptureCallback(callback);
+ verify(callback, timeout(3000).atLeastOnce()).onCaptureCompleted(
+ any(CameraCaptureSession.class),
+ any(CaptureRequest.class),
+ captureResultCaptor.capture());
+ List<TotalCaptureResult> totalCaptureResults = captureResultCaptor.getAllValues();
+ TotalCaptureResult result = totalCaptureResults.get(totalCaptureResults.size() - 1);
+
+ // Verify the exposure compensation target result is in the capture result.
+ assertThat(result.get(CaptureResult.CONTROL_AE_EXPOSURE_COMPENSATION)).isEqualTo(upper);
+ }
+
+ @Test
+ public void setExposureTest_runTwice()
+ throws InterruptedException, TimeoutException, ExecutionException,
+ CameraUseCaseAdapter.CameraException {
+ ExposureState exposureState = mCameraInfoInternal.getExposureState();
+ assumeTrue(exposureState.isExposureCompensationSupported());
+
+ FakeTestUseCase useCase = openUseCase();
+ // Wait a little bit for the camera to open.
+ assertTrue(mSessionStateCallback.waitForOnConfigured(1));
+
+ int upper = exposureState.getExposureCompensationRange().getUpper();
+
+ // Set the EC value first time.
+ mCameraControlInternal.setExposureCompensationIndex(upper - 1);
+
+ // Set the EC value again, and verify this task should complete successfully.
+ mCameraControlInternal.setExposureCompensationIndex(upper).get(3000, TimeUnit.MILLISECONDS);
+
+ ArgumentCaptor<TotalCaptureResult> captureResultCaptor = ArgumentCaptor.forClass(
+ TotalCaptureResult.class);
+ CameraCaptureSession.CaptureCallback callback = mock(
+ CameraCaptureSession.CaptureCallback.class);
+ useCase.setCameraCaptureCallback(callback);
+ verify(callback, timeout(3000).atLeastOnce()).onCaptureCompleted(
+ any(CameraCaptureSession.class),
+ any(CaptureRequest.class),
+ captureResultCaptor.capture());
+ List<TotalCaptureResult> totalCaptureResults = captureResultCaptor.getAllValues();
+ TotalCaptureResult result = totalCaptureResults.get(totalCaptureResults.size() - 1);
+
+ // Verify the exposure compensation target result is in the capture result.
+ assertThat(result.get(CaptureResult.CONTROL_AE_EXPOSURE_COMPENSATION)).isEqualTo(upper);
+ }
+
+ @Test
+ public void setExposureAndTriggerAe_theExposureSettingShouldApply()
+ throws InterruptedException, ExecutionException, TimeoutException,
+ CameraUseCaseAdapter.CameraException {
+ ExposureState exposureState = mCameraInfoInternal.getExposureState();
+ assumeTrue(exposureState.isExposureCompensationSupported());
+
+ FakeTestUseCase useCase = openUseCase();
+ ArgumentCaptor<TotalCaptureResult> captureResultCaptor = ArgumentCaptor.forClass(
+ TotalCaptureResult.class);
+ CameraCaptureSession.CaptureCallback callback = mock(
+ CameraCaptureSession.CaptureCallback.class);
+ useCase.setCameraCaptureCallback(callback);
+
+ // Wait a little bit for the camera to open.
+ assertTrue(mSessionStateCallback.waitForOnConfigured(1));
+
+ // Set the exposure compensation
+ int upper = exposureState.getExposureCompensationRange().getUpper();
+ mCameraControlInternal.setExposureCompensationIndex(upper).get(3000, TimeUnit.MILLISECONDS);
+ mCameraControlInternal.triggerAePrecapture().get(3000, TimeUnit.MILLISECONDS);
+
+ // Verify the exposure compensation target result is in the capture result.
+ verify(callback, timeout(3000).atLeastOnce()).onCaptureCompleted(
+ any(CameraCaptureSession.class),
+ any(CaptureRequest.class),
+ captureResultCaptor.capture());
+ List<TotalCaptureResult> totalCaptureResults = captureResultCaptor.getAllValues();
+ TotalCaptureResult result = totalCaptureResults.get(totalCaptureResults.size() - 1);
+ assertThat(result.get(CaptureResult.CONTROL_AE_EXPOSURE_COMPENSATION)).isEqualTo(upper);
+ }
+
+ @Test
+ public void setExposureAndTriggerAf_theExposureSettingShouldApply()
+ throws InterruptedException, ExecutionException, TimeoutException,
+ CameraUseCaseAdapter.CameraException {
+ ExposureState exposureState = mCameraInfoInternal.getExposureState();
+ assumeTrue(exposureState.isExposureCompensationSupported());
+
+ FakeTestUseCase useCase = openUseCase();
+ ArgumentCaptor<TotalCaptureResult> captureResultCaptor = ArgumentCaptor.forClass(
+ TotalCaptureResult.class);
+ CameraCaptureSession.CaptureCallback callback = mock(
+ CameraCaptureSession.CaptureCallback.class);
+ useCase.setCameraCaptureCallback(callback);
+
+ // Wait a little bit for the camera to open.
+ assertTrue(mSessionStateCallback.waitForOnConfigured(1));
+
+ int upper = exposureState.getExposureCompensationRange().getUpper();
+ mCameraControlInternal.setExposureCompensationIndex(upper).get(3000, TimeUnit.MILLISECONDS);
+ mCameraControlInternal.triggerAf().get(3000, TimeUnit.MILLISECONDS);
+
+ // Verify the exposure compensation target result is in the capture result.
+ verify(callback, timeout(3000).atLeastOnce()).onCaptureCompleted(
+ any(CameraCaptureSession.class),
+ any(CaptureRequest.class),
+ captureResultCaptor.capture());
+ List<TotalCaptureResult> totalCaptureResults = captureResultCaptor.getAllValues();
+ TotalCaptureResult result = totalCaptureResults.get(totalCaptureResults.size() - 1);
+ assertThat(result.get(CaptureResult.CONTROL_AE_EXPOSURE_COMPENSATION)).isEqualTo(upper);
+ }
+
+ @Test
+ public void setExposureAndZoomRatio_theExposureSettingShouldApply()
+ throws InterruptedException, ExecutionException, TimeoutException,
+ CameraUseCaseAdapter.CameraException {
+ ExposureState exposureState = mCameraInfoInternal.getExposureState();
+ assumeTrue(exposureState.isExposureCompensationSupported());
+
+ FakeTestUseCase useCase = openUseCase();
+ ArgumentCaptor<TotalCaptureResult> captureResultCaptor = ArgumentCaptor.forClass(
+ TotalCaptureResult.class);
+ CameraCaptureSession.CaptureCallback callback = mock(
+ CameraCaptureSession.CaptureCallback.class);
+ useCase.setCameraCaptureCallback(callback);
+
+ // Wait a little bit for the camera to open.
+ assertTrue(mSessionStateCallback.waitForOnConfigured(1));
+
+ int upper = exposureState.getExposureCompensationRange().getUpper();
+ mCameraControlInternal.setExposureCompensationIndex(upper).get(3000, TimeUnit.MILLISECONDS);
+ mCameraControlInternal.setZoomRatio(
+ mCameraInfoInternal.getZoomState().getValue().getMaxZoomRatio()).get(3000,
+ TimeUnit.MILLISECONDS);
+
+ // Verify the exposure compensation target result is in the capture result.
+ verify(callback, timeout(3000).atLeastOnce()).onCaptureCompleted(
+ any(CameraCaptureSession.class),
+ any(CaptureRequest.class),
+ captureResultCaptor.capture());
+ List<TotalCaptureResult> totalCaptureResults = captureResultCaptor.getAllValues();
+ TotalCaptureResult result = totalCaptureResults.get(totalCaptureResults.size() - 1);
+ assertThat(result.get(CaptureResult.CONTROL_AE_EXPOSURE_COMPENSATION)).isEqualTo(upper);
+ }
+
+ @Test
+ public void setExposureAndLinearZoom_theExposureSettingShouldApply()
+ throws InterruptedException, ExecutionException, TimeoutException,
+ CameraUseCaseAdapter.CameraException {
+ ExposureState exposureState = mCameraInfoInternal.getExposureState();
+ assumeTrue(exposureState.isExposureCompensationSupported());
+
+ FakeTestUseCase useCase = openUseCase();
+ ArgumentCaptor<TotalCaptureResult> captureResultCaptor = ArgumentCaptor.forClass(
+ TotalCaptureResult.class);
+ CameraCaptureSession.CaptureCallback callback = mock(
+ CameraCaptureSession.CaptureCallback.class);
+ useCase.setCameraCaptureCallback(callback);
+
+ // Wait a little bit for the camera to open.
+ assertTrue(mSessionStateCallback.waitForOnConfigured(1));
+
+ int upper = exposureState.getExposureCompensationRange().getUpper();
+ mCameraControlInternal.setExposureCompensationIndex(upper).get(3000, TimeUnit.MILLISECONDS);
+ mCameraControlInternal.setLinearZoom(0.5f).get(3000, TimeUnit.MILLISECONDS);
+
+ // Verify the exposure compensation target result is in the capture result.
+ verify(callback, timeout(3000).atLeastOnce()).onCaptureCompleted(
+ any(CameraCaptureSession.class),
+ any(CaptureRequest.class),
+ captureResultCaptor.capture());
+ List<TotalCaptureResult> totalCaptureResults = captureResultCaptor.getAllValues();
+ TotalCaptureResult result = totalCaptureResults.get(totalCaptureResults.size() - 1);
+ assertThat(result.get(CaptureResult.CONTROL_AE_EXPOSURE_COMPENSATION)).isEqualTo(upper);
+ }
+
+ @Test
+ public void setExposureAndFlash_theExposureSettingShouldApply()
+ throws InterruptedException, ExecutionException, TimeoutException,
+ CameraUseCaseAdapter.CameraException {
+ ExposureState exposureState = mCameraInfoInternal.getExposureState();
+ assumeTrue(exposureState.isExposureCompensationSupported());
+
+ FakeTestUseCase useCase = openUseCase();
+ ArgumentCaptor<TotalCaptureResult> captureResultCaptor = ArgumentCaptor.forClass(
+ TotalCaptureResult.class);
+ CameraCaptureSession.CaptureCallback callback = mock(
+ CameraCaptureSession.CaptureCallback.class);
+ useCase.setCameraCaptureCallback(callback);
+
+ // Wait a little bit for the camera to open.
+ assertTrue(mSessionStateCallback.waitForOnConfigured(1));
+
+ int upper = exposureState.getExposureCompensationRange().getUpper();
+ mCameraControlInternal.setExposureCompensationIndex(upper).get(3000, TimeUnit.MILLISECONDS);
+ mCameraControlInternal.setFlashMode(ImageCapture.FLASH_MODE_AUTO);
+
+ // Verify the exposure compensation target result is in the capture result.
+ verify(callback, timeout(3000).atLeastOnce()).onCaptureCompleted(
+ any(CameraCaptureSession.class),
+ any(CaptureRequest.class),
+ captureResultCaptor.capture());
+ List<TotalCaptureResult> totalCaptureResults = captureResultCaptor.getAllValues();
+ TotalCaptureResult result = totalCaptureResults.get(totalCaptureResults.size() - 1);
+ assertThat(result.get(CaptureResult.CONTROL_AE_EXPOSURE_COMPENSATION)).isEqualTo(upper);
+ }
+
+ @Test
+ public void setExposureTimeout_theNextCallShouldWork()
+ throws InterruptedException, ExecutionException, TimeoutException,
+ CameraUseCaseAdapter.CameraException {
+ ExposureState exposureState = mCameraInfoInternal.getExposureState();
+ assumeTrue(exposureState.isExposureCompensationSupported());
+
+ openUseCase();
+ // Wait a little bit for the camera to open.
+ assertTrue(mSessionStateCallback.waitForOnConfigured(1));
+
+ try {
+ // The set future should timeout in this test.
+ mCameraControlInternal.setExposureCompensationIndex(1).get(0, TimeUnit.MILLISECONDS);
+ } catch (Exception e) {
+ assertThat(e).isInstanceOf(TimeoutException.class);
+ }
+
+ // Verify the second time call should set the new exposure value successfully.
+ assertThat(mCameraControlInternal.setExposureCompensationIndex(2).get(3000,
+ TimeUnit.MILLISECONDS)).isEqualTo(2);
+ }
+
+ public static class FakeTestUseCase extends FakeUseCase {
+ private FakeUseCaseConfig mConfig;
+ private DeferrableSurface mDeferrableSurface;
+ private CameraCaptureSession.StateCallback mSessionStateCallback;
+ CameraCaptureSession.CaptureCallback mCameraCaptureCallback;
+
+ FakeTestUseCase(
+ @NonNull FakeUseCaseConfig config,
+ @NonNull CameraInternal cameraInternal,
+ @NonNull CameraCaptureSession.StateCallback sessionStateCallback) {
+ super(config);
+ // Ensure we're using the combined configuration (user config + defaults)
+ mConfig = (FakeUseCaseConfig) getUseCaseConfig();
+
+ mSessionStateCallback = sessionStateCallback;
+ }
+
+ public void setCameraCaptureCallback(
+ CameraCaptureSession.CaptureCallback cameraCaptureCallback) {
+ mCameraCaptureCallback = cameraCaptureCallback;
+ }
+
+ @Override
+ public void clear() {
+ super.clear();
+ if (mDeferrableSurface != null) {
+ mDeferrableSurface.close();
+ }
+ }
+
+ @Override
+ @NonNull
+ protected Size onSuggestedResolutionUpdated(
+ @NonNull Size suggestedResolution) {
+ createPipeline(suggestedResolution);
+ notifyActive();
+ return suggestedResolution;
+ }
+
+ private void createPipeline(Size resolution) {
+ SessionConfig.Builder builder = SessionConfig.Builder.createFrom(mConfig);
+
+ builder.setTemplateType(CameraDevice.TEMPLATE_PREVIEW);
+ if (mDeferrableSurface != null) {
+ mDeferrableSurface.close();
+ }
+
+ // Create the metering DeferrableSurface
+ SurfaceTexture surfaceTexture = new SurfaceTexture(0);
+ surfaceTexture.setDefaultBufferSize(resolution.getWidth(), resolution.getHeight());
+ Surface surface = new Surface(surfaceTexture);
+
+ mDeferrableSurface = new ImmediateSurface(surface);
+ mDeferrableSurface.getTerminationFuture().addListener(() -> {
+ surface.release();
+ surfaceTexture.release();
+ }, CameraXExecutors.directExecutor());
+ builder.addSurface(mDeferrableSurface);
+ builder.addSessionStateCallback(mSessionStateCallback);
+ builder.addRepeatingCameraCaptureCallback(CaptureCallbackContainer.create(
+ new CameraCaptureSession.CaptureCallback() {
+ @Override
+ public void onCaptureCompleted(@NonNull CameraCaptureSession session,
+ @NonNull CaptureRequest request,
+ @NonNull TotalCaptureResult result) {
+ if (mCameraCaptureCallback != null) {
+ mCameraCaptureCallback.onCaptureCompleted(session, request, result);
+ }
+ }
+ }));
+
+ builder.addErrorListener((sessionConfig, error) -> {
+ // Create new pipeline and it will close the old one.
+ createPipeline(resolution);
+ });
+ updateSessionConfig(builder.build());
+ }
+ }
+}
diff --git a/camera/camera-camera2/src/main/java/androidx/camera/camera2/internal/Camera2CameraControl.java b/camera/camera-camera2/src/main/java/androidx/camera/camera2/internal/Camera2CameraControl.java
index 2bef42b..abd75ba 100644
--- a/camera/camera-camera2/src/main/java/androidx/camera/camera2/internal/Camera2CameraControl.java
+++ b/camera/camera-camera2/src/main/java/androidx/camera/camera2/internal/Camera2CameraControl.java
@@ -37,6 +37,7 @@
import androidx.annotation.VisibleForTesting;
import androidx.camera.camera2.impl.Camera2ImplConfig;
import androidx.camera.camera2.internal.annotation.CameraExecutor;
+import androidx.camera.core.ExperimentalExposureCompensation;
import androidx.camera.core.FocusMeteringAction;
import androidx.camera.core.FocusMeteringResult;
import androidx.camera.core.ImageCapture;
@@ -107,6 +108,7 @@
private final FocusMeteringControl mFocusMeteringControl;
private final ZoomControl mZoomControl;
private final TorchControl mTorchControl;
+ private final ExposureControl mExposureControl;
private final AeFpsRange mAeFpsRange;
@GuardedBy("mLock")
private int mUseCount = 0;
@@ -149,6 +151,7 @@
// CameraCaptureCallback efficiently.
mSessionConfigBuilder.addRepeatingCameraCaptureCallback(mCameraCaptureCallbackSet);
+ mExposureControl = new ExposureControl(this, mCameraCharacteristics, mExecutor);
mFocusMeteringControl = new FocusMeteringControl(this, scheduler, mExecutor);
mZoomControl = new ZoomControl(this, mCameraCharacteristics, mExecutor);
mTorchControl = new TorchControl(this, mCameraCharacteristics, mExecutor);
@@ -204,6 +207,11 @@
return mTorchControl;
}
+ @NonNull
+ public ExposureControl getExposureControl() {
+ return mExposureControl;
+ }
+
/**
* Set current active state. Set active if it is ready to trigger camera control operation.
*
@@ -215,6 +223,7 @@
mFocusMeteringControl.setActive(isActive);
mZoomControl.setActive(isActive);
mTorchControl.setActive(isActive);
+ mExposureControl.setActive(isActive);
}
@ExecutedBy("mExecutor")
@@ -369,6 +378,17 @@
cancelAePrecaptureTrigger));
}
+ @NonNull
+ @Override
+ @ExperimentalExposureCompensation
+ public ListenableFuture<Integer> setExposureCompensationIndex(int exposure) {
+ if (!isControlInUse()) {
+ return Futures.immediateFailedFuture(
+ new OperationCanceledException("Camera is not active."));
+ }
+ return mExposureControl.setExposureCompensationIndex(exposure);
+ }
+
/** {@inheritDoc} */
@Override
public void submitCaptureRequests(@NonNull final List<CaptureConfig> captureConfigs) {
@@ -515,6 +535,8 @@
builder.setCaptureRequestOption(CaptureRequest.SCALER_CROP_REGION, mCropRect);
}
+ mExposureControl.setCaptureRequestOption(builder);
+
return builder.build();
}
diff --git a/camera/camera-camera2/src/main/java/androidx/camera/camera2/internal/Camera2CameraInfoImpl.java b/camera/camera-camera2/src/main/java/androidx/camera/camera2/internal/Camera2CameraInfoImpl.java
index d0b8888..d59d58b 100644
--- a/camera/camera-camera2/src/main/java/androidx/camera/camera2/internal/Camera2CameraInfoImpl.java
+++ b/camera/camera-camera2/src/main/java/androidx/camera/camera2/internal/Camera2CameraInfoImpl.java
@@ -24,6 +24,8 @@
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.camera.core.CameraSelector;
+import androidx.camera.core.ExperimentalExposureCompensation;
+import androidx.camera.core.ExposureState;
import androidx.camera.core.ZoomState;
import androidx.camera.core.impl.CameraCaptureCallback;
import androidx.camera.core.impl.CameraInfoInternal;
@@ -46,6 +48,7 @@
private final Camera2CameraControl mCamera2CameraControl;
private final ZoomControl mZoomControl;
private final TorchControl mTorchControl;
+ private final ExposureControl mExposureControl;
Camera2CameraInfoImpl(@NonNull String cameraId,
@NonNull CameraCharacteristics cameraCharacteristics,
@@ -56,6 +59,7 @@
mCamera2CameraControl = camera2CameraControl;
mZoomControl = camera2CameraControl.getZoomControl();
mTorchControl = camera2CameraControl.getTorchControl();
+ mExposureControl = camera2CameraControl.getExposureControl();
logDeviceInfo();
}
@@ -173,6 +177,13 @@
return mZoomControl.getZoomState();
}
+ @NonNull
+ @Override
+ @ExperimentalExposureCompensation
+ public ExposureState getExposureState() {
+ return mExposureControl.getExposureState();
+ }
+
/**
* {@inheritDoc}
*
diff --git a/camera/camera-camera2/src/main/java/androidx/camera/camera2/internal/ExposureControl.java b/camera/camera-camera2/src/main/java/androidx/camera/camera2/internal/ExposureControl.java
new file mode 100644
index 0000000..e407592
--- /dev/null
+++ b/camera/camera-camera2/src/main/java/androidx/camera/camera2/internal/ExposureControl.java
@@ -0,0 +1,227 @@
+/*
+ * Copyright 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.camera.camera2.internal;
+
+import android.hardware.camera2.CameraCharacteristics;
+import android.hardware.camera2.CaptureRequest;
+import android.hardware.camera2.CaptureResult;
+import android.util.Range;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.annotation.experimental.UseExperimental;
+import androidx.camera.camera2.impl.Camera2ImplConfig;
+import androidx.camera.camera2.internal.annotation.CameraExecutor;
+import androidx.camera.core.CameraControl;
+import androidx.camera.core.ExposureState;
+import androidx.camera.core.impl.annotation.ExecutedBy;
+import androidx.camera.core.impl.utils.futures.Futures;
+import androidx.concurrent.futures.CallbackToFutureAdapter;
+import androidx.core.util.Preconditions;
+
+import com.google.common.util.concurrent.ListenableFuture;
+
+import java.util.concurrent.Executor;
+
+/**
+ * Implementation of Exposure compensation control.
+ *
+ * <p>It is intended to be used within {@link Camera2CameraControl} to implement the
+ * functionality of {@link Camera2CameraControl#setExposureCompensationIndex(int)}.
+ *
+ * <p>To wait for the exposure setting reach to the new requested target, it calls
+ * {@link Camera2CameraControl#addCaptureResultListener(Camera2CameraControl.CaptureResultListener)}
+ * to monitor the capture result.
+ *
+ * <p>The {@link Camera2CameraControl#setExposureCompensationIndex(int)} can only allow to run one
+ * task at the same time, it will cancel the incomplete task if a new task is requested. The
+ * task will fails with {@link CameraControl.OperationCanceledException} if the camera is closed.
+ */
+@UseExperimental(markerClass = androidx.camera.core.ExperimentalExposureCompensation.class)
+public class ExposureControl {
+
+ private static final int DEFAULT_EXPOSURE_COMPENSATION = 0;
+
+ @NonNull
+ private final Camera2CameraControl mCameraControl;
+
+ @NonNull
+ private final ExposureStateImpl mExposureStateImpl;
+
+ @NonNull
+ @CameraExecutor
+ private final Executor mExecutor;
+
+ private boolean mIsActive = false;
+
+ @Nullable
+ private CallbackToFutureAdapter.Completer<Integer> mRunningCompleter;
+ @Nullable
+ private Camera2CameraControl.CaptureResultListener mRunningCaptureResultListener;
+
+ /**
+ * Constructs a ExposureControl.
+ *
+ * <p>All tasks executed by {@code executor}.
+ *
+ * @param cameraControl Camera control.
+ * @param cameraCharacteristics The {@link CameraCharacteristics} of the camera.
+ * @param executor the camera executor used to run camera task.
+ */
+ ExposureControl(@NonNull Camera2CameraControl cameraControl,
+ @NonNull CameraCharacteristics cameraCharacteristics,
+ @CameraExecutor @NonNull Executor executor) {
+ mCameraControl = cameraControl;
+ mExposureStateImpl = new ExposureStateImpl(cameraCharacteristics,
+ DEFAULT_EXPOSURE_COMPENSATION);
+ mExecutor = executor;
+ }
+
+ /**
+ * Set current active state. Set active if it is ready to accept operations.
+ *
+ * <p>Set the active state to false will cancel the in fly
+ * {@link #setExposureCompensationIndex(int)} task with
+ * {@link CameraControl.OperationCanceledException}.
+ */
+ @ExecutedBy("mExecutor")
+ void setActive(boolean isActive) {
+ if (isActive == mIsActive) {
+ return;
+ }
+
+ mIsActive = isActive;
+
+ if (!mIsActive) {
+ mExposureStateImpl.setExposureCompensationIndex(DEFAULT_EXPOSURE_COMPENSATION);
+ clearRunningTask();
+ }
+ }
+
+ /**
+ * Called by {@link Camera2CameraControl} to append the CONTROL_AE_EXPOSURE_COMPENSATION option
+ * to the shared options. It applies to all repeating requests and single requests.
+ */
+ @ExecutedBy("mExecutor")
+ void setCaptureRequestOption(@NonNull Camera2ImplConfig.Builder configBuilder) {
+ configBuilder.setCaptureRequestOption(CaptureRequest.CONTROL_AE_EXPOSURE_COMPENSATION,
+ mExposureStateImpl.getExposureCompensationIndex());
+ }
+
+ @NonNull
+ ExposureState getExposureState() {
+ return mExposureStateImpl;
+ }
+
+ @NonNull
+ ListenableFuture<Integer> setExposureCompensationIndex(int exposure) {
+ if (!mExposureStateImpl.isExposureCompensationSupported()) {
+ return Futures.immediateFailedFuture(new IllegalArgumentException(
+ "ExposureCompensation is not supported"));
+ }
+
+ Range<Integer> range = mExposureStateImpl.getExposureCompensationRange();
+ if (!range.contains(exposure)) {
+ return Futures.immediateFailedFuture(new IllegalArgumentException(
+ "Requested ExposureCompensation " + exposure + " is not within"
+ + " valid range [" + range.getUpper() + ".." + range.getLower() + "]"));
+ }
+
+ // Set the new exposure value to the ExposureState immediately.
+ mExposureStateImpl.setExposureCompensationIndex(exposure);
+
+ return Futures.nonCancellationPropagating(CallbackToFutureAdapter.getFuture(
+ completer -> {
+ mExecutor.execute(() -> {
+ if (!mIsActive) {
+ mExposureStateImpl.setExposureCompensationIndex(
+ DEFAULT_EXPOSURE_COMPENSATION);
+ completer.setException(new CameraControl.OperationCanceledException(
+ "Camera is not active."));
+ return;
+ }
+
+ clearRunningTask();
+
+ Preconditions.checkState(mRunningCompleter == null, "mRunningCompleter "
+ + "should be null when starting set a new exposure compensation "
+ + "value");
+ Preconditions.checkState(mRunningCaptureResultListener == null,
+ "mRunningCaptureResultListener "
+ + "should be null when starting set a new exposure "
+ + "compensation value");
+
+ mRunningCaptureResultListener =
+ captureResult -> {
+ Integer state = captureResult.get(
+ CaptureResult.CONTROL_AE_STATE);
+ Integer evResult = captureResult.get(
+ CaptureResult.CONTROL_AE_EXPOSURE_COMPENSATION);
+ if (state != null && evResult != null) {
+ switch (state) {
+ case CaptureResult.CONTROL_AE_STATE_FLASH_REQUIRED:
+ case CaptureResult.CONTROL_AE_STATE_CONVERGED:
+ case CaptureResult.CONTROL_AE_STATE_LOCKED:
+ if (evResult == exposure) {
+ completer.set(exposure);
+ // Only remove the capture result listener,
+ // the mRunningCompleter and
+ // mRunningCaptureResultListener will be
+ // cleared before the next set exposure task.
+ return true;
+ }
+ break;
+ default:
+ // Ignore other results.
+ }
+ } else if (evResult != null && evResult == exposure) {
+ // If AE state is null, only wait for the exposure result
+ // to the desired value.
+ completer.set(exposure);
+
+ // Only remove the capture result listener, the
+ // mRunningCompleter and mRunningCaptureResultListener
+ // will be cleared before the next set exposure task.
+ return true;
+ }
+ return false;
+ };
+ mRunningCompleter = completer;
+
+ mCameraControl.addCaptureResultListener(mRunningCaptureResultListener);
+ mCameraControl.updateSessionConfig();
+ });
+
+ return "setExposureCompensationIndex[" + exposure + "]";
+ }));
+ }
+
+ @ExecutedBy("mExecutor")
+ private void clearRunningTask() {
+ if (mRunningCompleter != null) {
+ mRunningCompleter.setException(
+ new CameraControl.OperationCanceledException(
+ "Cancelled by another setExposureCompensationIndex()"));
+ mRunningCompleter = null;
+ }
+
+ if (mRunningCaptureResultListener != null) {
+ mCameraControl.removeCaptureResultListener(mRunningCaptureResultListener);
+ mRunningCaptureResultListener = null;
+ }
+ }
+}
diff --git a/camera/camera-camera2/src/main/java/androidx/camera/camera2/internal/ExposureStateImpl.java b/camera/camera-camera2/src/main/java/androidx/camera/camera2/internal/ExposureStateImpl.java
new file mode 100644
index 0000000..674b180
--- /dev/null
+++ b/camera/camera-camera2/src/main/java/androidx/camera/camera2/internal/ExposureStateImpl.java
@@ -0,0 +1,79 @@
+/*
+ * Copyright 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.camera.camera2.internal;
+
+import android.hardware.camera2.CameraCharacteristics;
+import android.util.Range;
+import android.util.Rational;
+
+import androidx.annotation.GuardedBy;
+import androidx.annotation.NonNull;
+import androidx.camera.core.ExperimentalExposureCompensation;
+import androidx.camera.core.ExposureState;
+
+/**
+ * An implementation of {@link ExposureState} where the values can be set.
+ */
+@ExperimentalExposureCompensation
+class ExposureStateImpl implements ExposureState {
+
+ private final Object mLock = new Object();
+ private final CameraCharacteristics mCameraCharacteristics;
+ @GuardedBy("mLock")
+ private int mExposureCompensation;
+
+ ExposureStateImpl(CameraCharacteristics characteristics, int exposureCompensation) {
+ mCameraCharacteristics = characteristics;
+ mExposureCompensation = exposureCompensation;
+ }
+
+ @Override
+ public int getExposureCompensationIndex() {
+ synchronized (mLock) {
+ return mExposureCompensation;
+ }
+ }
+
+ void setExposureCompensationIndex(int value) {
+ synchronized (mLock) {
+ mExposureCompensation = value;
+ }
+ }
+
+ @NonNull
+ @Override
+ public Range<Integer> getExposureCompensationRange() {
+ return mCameraCharacteristics.get(CameraCharacteristics.CONTROL_AE_COMPENSATION_RANGE);
+ }
+
+ @NonNull
+ @Override
+ public Rational getExposureCompensationStep() {
+ if (!isExposureCompensationSupported()) {
+ return Rational.ZERO;
+ }
+ return mCameraCharacteristics.get(
+ CameraCharacteristics.CONTROL_AE_COMPENSATION_STEP);
+ }
+
+ @Override
+ public boolean isExposureCompensationSupported() {
+ Range<Integer> compensationRange =
+ mCameraCharacteristics.get(CameraCharacteristics.CONTROL_AE_COMPENSATION_RANGE);
+ return compensationRange.getLower() != 0 && compensationRange.getUpper() != 0;
+ }
+}
diff --git a/camera/camera-camera2/src/main/java/androidx/camera/camera2/internal/compat/CameraAccessExceptionCompat.java b/camera/camera-camera2/src/main/java/androidx/camera/camera2/internal/compat/CameraAccessExceptionCompat.java
index 6ef2237..c5f93fe 100644
--- a/camera/camera-camera2/src/main/java/androidx/camera/camera2/internal/compat/CameraAccessExceptionCompat.java
+++ b/camera/camera-camera2/src/main/java/androidx/camera/camera2/internal/compat/CameraAccessExceptionCompat.java
@@ -18,6 +18,7 @@
import android.app.NotificationManager;
import android.hardware.camera2.CameraAccessException;
+import android.hardware.camera2.CameraCharacteristics;
import android.hardware.camera2.CameraDevice;
import androidx.annotation.IntDef;
@@ -115,9 +116,16 @@
*/
public static final int CAMERA_UNAVAILABLE_DO_NOT_DISTURB = 10001;
+ /**
+ * Error occurs when creating {@link CameraCharacteristics}. Some devices may throw
+ * {@link AssertionError} when creating CameraCharacteristics and FPS ranges are null.
+ */
+ public static final int CAMERA_CHARACTERISTICS_CREATION_ERROR = 10002;
+
@VisibleForTesting
static final Set<Integer> COMPAT_ERRORS = Collections.unmodifiableSet(
- new HashSet<>(Arrays.asList(CAMERA_UNAVAILABLE_DO_NOT_DISTURB)));
+ new HashSet<>(Arrays.asList(CAMERA_UNAVAILABLE_DO_NOT_DISTURB,
+ CAMERA_CHARACTERISTICS_CREATION_ERROR)));
// End of the CameraAccessExceptionCompat error
// *********************************************************************************************
@@ -137,7 +145,8 @@
CAMERA_ERROR,
// Start of the compat error
- CAMERA_UNAVAILABLE_DO_NOT_DISTURB
+ CAMERA_UNAVAILABLE_DO_NOT_DISTURB,
+ CAMERA_CHARACTERISTICS_CREATION_ERROR
})
public @interface AccessError {
}
@@ -234,6 +243,9 @@
return "Some API 28 devices cannot access the camera when the device is in \"Do "
+ "Not Disturb\" mode. The camera will not be accessible until \"Do Not "
+ "Disturb\" mode is disabled.";
+
+ case CAMERA_CHARACTERISTICS_CREATION_ERROR:
+ return "Failed to create CameraCharacteristics.";
}
return null;
}
@@ -270,6 +282,9 @@
case CAMERA_UNAVAILABLE_DO_NOT_DISTURB:
problemString = "CAMERA_UNAVAILABLE_DO_NOT_DISTURB";
break;
+ case CAMERA_CHARACTERISTICS_CREATION_ERROR:
+ problemString = "CAMERA_CHARACTERISTICS_CREATION_ERROR";
+ break;
default:
problemString = "<UNKNOWN ERROR>";
}
diff --git a/camera/camera-camera2/src/main/java/androidx/camera/camera2/internal/compat/CameraManagerCompat.java b/camera/camera-camera2/src/main/java/androidx/camera/camera2/internal/compat/CameraManagerCompat.java
index 765b09f..d6fb116 100644
--- a/camera/camera-camera2/src/main/java/androidx/camera/camera2/internal/compat/CameraManagerCompat.java
+++ b/camera/camera-camera2/src/main/java/androidx/camera/camera2/internal/compat/CameraManagerCompat.java
@@ -152,7 +152,17 @@
@NonNull
public CameraCharacteristics getCameraCharacteristics(@NonNull String cameraId)
throws CameraAccessExceptionCompat {
- return mImpl.getCameraCharacteristics(cameraId);
+
+ try {
+ return mImpl.getCameraCharacteristics(cameraId);
+ } catch (AssertionError e) {
+ // Some devices may throw AssertionError when creating CameraCharacteristics and FPS
+ // ranges are null. Catch the AssertionError and throw a CameraAccessExceptionCompat
+ // to make the app be able to receive an exception to gracefully handle it.
+ throw new CameraAccessExceptionCompat(
+ CameraAccessExceptionCompat.CAMERA_CHARACTERISTICS_CREATION_ERROR,
+ e.getMessage(), e);
+ }
}
/**
diff --git a/camera/camera-camera2/src/test/java/androidx/camera/camera2/internal/ExposureControlTest.java b/camera/camera-camera2/src/test/java/androidx/camera/camera2/internal/ExposureControlTest.java
new file mode 100644
index 0000000..55b54407
--- /dev/null
+++ b/camera/camera-camera2/src/test/java/androidx/camera/camera2/internal/ExposureControlTest.java
@@ -0,0 +1,203 @@
+/*
+ * Copyright 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.camera.camera2.internal;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.junit.Assert.assertFalse;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.spy;
+
+import android.content.Context;
+import android.hardware.camera2.CameraAccessException;
+import android.hardware.camera2.CameraCharacteristics;
+import android.hardware.camera2.CameraManager;
+import android.os.Build;
+import android.util.Range;
+import android.util.Rational;
+
+import androidx.annotation.experimental.UseExperimental;
+import androidx.camera.core.CameraControl;
+import androidx.camera.core.ExperimentalExposureCompensation;
+import androidx.camera.core.impl.CameraControlInternal;
+import androidx.camera.core.impl.utils.executor.CameraXExecutors;
+import androidx.test.core.app.ApplicationProvider;
+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.shadow.api.Shadow;
+import org.robolectric.shadows.ShadowCameraCharacteristics;
+import org.robolectric.shadows.ShadowCameraManager;
+
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
+
+@SmallTest
+@RunWith(RobolectricTestRunner.class)
+@Config(minSdk = Build.VERSION_CODES.LOLLIPOP)
+@DoNotInstrument
+@UseExperimental(markerClass = ExperimentalExposureCompensation.class)
+public class ExposureControlTest {
+
+ private static final String CAMERA0_ID = "0";
+ private static final String CAMERA1_ID = "1";
+
+ private ExposureControl mExposureControl;
+ private Camera2CameraControl mCamera2CameraControl;
+
+ @Before
+ public void setUp() throws CameraAccessException {
+ initCameras();
+
+ CameraControlInternal.ControlUpdateCallback updateCallback = mock(
+ CameraControlInternal.ControlUpdateCallback.class);
+
+ CameraManager cameraManager =
+ (CameraManager) ApplicationProvider.getApplicationContext().getSystemService(
+ Context.CAMERA_SERVICE);
+ CameraCharacteristics cameraCharacteristics =
+ cameraManager.getCameraCharacteristics(CAMERA0_ID);
+
+ mCamera2CameraControl = spy(new Camera2CameraControl(
+ cameraCharacteristics,
+ CameraXExecutors.mainThreadExecutor(),
+ CameraXExecutors.directExecutor(),
+ updateCallback));
+
+ mExposureControl = new ExposureControl(mCamera2CameraControl, cameraCharacteristics,
+ CameraXExecutors.directExecutor());
+ mExposureControl.setActive(true);
+ }
+
+ private void initCameras() {
+ CameraCharacteristics characteristics0 =
+ ShadowCameraCharacteristics.newCameraCharacteristics();
+ ShadowCameraCharacteristics shadowCharacteristics0 = Shadow.extract(characteristics0);
+ shadowCharacteristics0.set(CameraCharacteristics.CONTROL_AE_COMPENSATION_RANGE,
+ Range.create(-4, 4));
+ shadowCharacteristics0.set(CameraCharacteristics.CONTROL_AE_COMPENSATION_STEP,
+ Rational.parseRational("1/2"));
+
+ CameraCharacteristics characteristics1 =
+ ShadowCameraCharacteristics.newCameraCharacteristics();
+ ShadowCameraCharacteristics shadowCharacteristics1 = Shadow.extract(characteristics1);
+ shadowCharacteristics1.set(CameraCharacteristics.CONTROL_AE_COMPENSATION_RANGE,
+ Range.create(-0, 0));
+
+ // Add the camera to the camera service
+ ShadowCameraManager shadowCameraManager = Shadow.extract(
+ ApplicationProvider.getApplicationContext().getSystemService(
+ Context.CAMERA_SERVICE));
+
+ shadowCameraManager.addCamera(CAMERA0_ID, characteristics0);
+ shadowCameraManager.addCamera(CAMERA1_ID, characteristics1);
+ }
+
+ @Test
+ public void setExposureTwice_theFirstCallShouldBeCancelled() throws InterruptedException {
+ ListenableFuture<Integer> future1 = mExposureControl.setExposureCompensationIndex(1);
+ ListenableFuture<Integer> future2 = mExposureControl.setExposureCompensationIndex(2);
+
+ // The second call should keep working.
+ assertFalse(future2.isDone());
+
+ // The first call should be cancelled with a CameraControl.OperationCanceledException.
+ try {
+ future1.get();
+ } catch (ExecutionException e) {
+ assertThat(e.getCause()).isInstanceOf(
+ CameraControlInternal.OperationCanceledException.class);
+ }
+ }
+
+ @Test
+ public void setExposureTimeout_theCompensationValueShouldKeepInControl() {
+ ListenableFuture<Integer> future1 = mExposureControl.setExposureCompensationIndex(1);
+
+ try {
+ // The set future should timeout in this test.
+ future1.get(0, TimeUnit.MILLISECONDS);
+ } catch (Exception e) {
+ assertThat(e).isInstanceOf(TimeoutException.class);
+ }
+
+ // The new value should be set to the exposure control even when the ListenableFuture
+ // task fails.
+ assertThat(mExposureControl.getExposureState().getExposureCompensationIndex()).isEqualTo(1);
+ }
+
+ @Test
+ public void exposureControlInactive_setExposureTaskShouldCancel()
+ throws InterruptedException, TimeoutException {
+ ListenableFuture<Integer> future = mExposureControl.setExposureCompensationIndex(1);
+ mExposureControl.setActive(false);
+
+ try {
+ // The exposure control has been set to inactive. It should throw the exception.
+ future.get(3000, TimeUnit.MILLISECONDS);
+ } catch (ExecutionException e) {
+ assertThat(e.getCause()).isInstanceOf(CameraControl.OperationCanceledException.class);
+ }
+ }
+
+ @Test
+ public void setExposureNotInRange_shouldCompleteTheTaskWithException()
+ throws InterruptedException, TimeoutException {
+ try {
+ // The Exposure index to 5 not in the valid range. It should throw the exception.
+ mExposureControl.setExposureCompensationIndex(5).get(3000, TimeUnit.MILLISECONDS);
+ } catch (ExecutionException e) {
+ assertThat(e.getCause()).isInstanceOf(IllegalArgumentException.class);
+ }
+ }
+
+ @Test
+ public void setExposureOnNotSupportedCamera_shouldCompleteTheTaskWithException()
+ throws CameraAccessException, InterruptedException, TimeoutException {
+ CameraManager cameraManager =
+ (CameraManager) ApplicationProvider.getApplicationContext().getSystemService(
+ Context.CAMERA_SERVICE);
+ CameraCharacteristics cameraCharacteristics =
+ cameraManager.getCameraCharacteristics(CAMERA1_ID);
+
+ mCamera2CameraControl = spy(new Camera2CameraControl(
+ cameraCharacteristics,
+ CameraXExecutors.mainThreadExecutor(),
+ CameraXExecutors.directExecutor(),
+ mock(CameraControlInternal.ControlUpdateCallback.class)));
+
+ mExposureControl = new ExposureControl(mCamera2CameraControl, cameraCharacteristics,
+ CameraXExecutors.directExecutor());
+ mExposureControl.setActive(true);
+
+ ListenableFuture<Integer> future = mExposureControl.setExposureCompensationIndex(1);
+ try {
+ // This camera does not support the exposure compensation, the task should fail.
+ future.get(3000, TimeUnit.MILLISECONDS);
+ } catch (ExecutionException e) {
+ assertThat(e.getCause()).isInstanceOf(IllegalArgumentException.class);
+ }
+ }
+}
diff --git a/camera/camera-core/api/public_plus_experimental_1.0.0-beta09.txt b/camera/camera-core/api/public_plus_experimental_1.0.0-beta09.txt
index 26f117f..9c7f729 100644
--- a/camera/camera-core/api/public_plus_experimental_1.0.0-beta09.txt
+++ b/camera/camera-core/api/public_plus_experimental_1.0.0-beta09.txt
@@ -14,6 +14,7 @@
public interface CameraControl {
method public com.google.common.util.concurrent.ListenableFuture<java.lang.Void!> cancelFocusAndMetering();
method public com.google.common.util.concurrent.ListenableFuture<java.lang.Void!> enableTorch(boolean);
+ method @androidx.camera.core.ExperimentalExposureCompensation public com.google.common.util.concurrent.ListenableFuture<java.lang.Integer!> setExposureCompensationIndex(int);
method public com.google.common.util.concurrent.ListenableFuture<java.lang.Void!> setLinearZoom(@FloatRange(from=0.0f, to=1.0f) float);
method public com.google.common.util.concurrent.ListenableFuture<java.lang.Void!> setZoomRatio(float);
method public com.google.common.util.concurrent.ListenableFuture<androidx.camera.core.FocusMeteringResult!> startFocusAndMetering(androidx.camera.core.FocusMeteringAction);
@@ -27,6 +28,7 @@
}
public interface CameraInfo {
+ method @androidx.camera.core.ExperimentalExposureCompensation public androidx.camera.core.ExposureState getExposureState();
method public int getSensorRotationDegrees();
method public int getSensorRotationDegrees(int);
method public androidx.lifecycle.LiveData<java.lang.Integer!> getTorchState();
@@ -90,12 +92,22 @@
@experimental.Experimental @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) public @interface ExperimentalCustomizableThreads {
}
+ @experimental.Experimental @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) public @interface ExperimentalExposureCompensation {
+ }
+
@experimental.Experimental @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) public @interface ExperimentalGetImage {
}
@experimental.Experimental @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) public @interface ExperimentalUseCaseGroup {
}
+ @androidx.camera.core.ExperimentalExposureCompensation public interface ExposureState {
+ method public int getExposureCompensationIndex();
+ method public android.util.Range<java.lang.Integer!> getExposureCompensationRange();
+ method public android.util.Rational getExposureCompensationStep();
+ method public boolean isExposureCompensationSupported();
+ }
+
public interface ExtendableBuilder<T> {
method public T build();
}
@@ -269,6 +281,7 @@
method public int getTargetRotation();
method @UiThread public void setSurfaceProvider(java.util.concurrent.Executor, androidx.camera.core.Preview.SurfaceProvider?);
method @UiThread public void setSurfaceProvider(androidx.camera.core.Preview.SurfaceProvider?);
+ method @androidx.camera.core.ExperimentalUseCaseGroup public void setTargetRotation(int);
}
public static final class Preview.Builder implements androidx.camera.core.ExtendableBuilder<androidx.camera.core.Preview> {
@@ -291,9 +304,10 @@
public final class SurfaceRequest {
method public void addRequestCancellationListener(java.util.concurrent.Executor, Runnable);
- method @androidx.camera.core.ExperimentalUseCaseGroup public android.graphics.Rect getCropRect();
+ method @androidx.camera.core.ExperimentalUseCaseGroup public void clearTransformationInfoListener();
method public android.util.Size getResolution();
method public void provideSurface(android.view.Surface, java.util.concurrent.Executor, androidx.core.util.Consumer<androidx.camera.core.SurfaceRequest.Result!>);
+ method @androidx.camera.core.ExperimentalUseCaseGroup public void setTransformationInfoListener(java.util.concurrent.Executor, androidx.camera.core.SurfaceRequest.TransformationInfoListener);
method public boolean willNotProvideSurface();
}
@@ -307,6 +321,15 @@
field public static final int RESULT_WILL_NOT_PROVIDE_SURFACE = 4; // 0x4
}
+ @androidx.camera.core.ExperimentalUseCaseGroup @com.google.auto.value.AutoValue public abstract static class SurfaceRequest.TransformationInfo {
+ method public abstract android.graphics.Rect getCropRect();
+ method public abstract int getRotationDegrees();
+ }
+
+ @androidx.camera.core.ExperimentalUseCaseGroup public static interface SurfaceRequest.TransformationInfoListener {
+ method public void onTransformationInfoUpdate(androidx.camera.core.SurfaceRequest.TransformationInfo);
+ }
+
public class TorchState {
field public static final int OFF = 0; // 0x0
field public static final int ON = 1; // 0x1
diff --git a/camera/camera-core/api/public_plus_experimental_current.txt b/camera/camera-core/api/public_plus_experimental_current.txt
index 26f117f..9c7f729 100644
--- a/camera/camera-core/api/public_plus_experimental_current.txt
+++ b/camera/camera-core/api/public_plus_experimental_current.txt
@@ -14,6 +14,7 @@
public interface CameraControl {
method public com.google.common.util.concurrent.ListenableFuture<java.lang.Void!> cancelFocusAndMetering();
method public com.google.common.util.concurrent.ListenableFuture<java.lang.Void!> enableTorch(boolean);
+ method @androidx.camera.core.ExperimentalExposureCompensation public com.google.common.util.concurrent.ListenableFuture<java.lang.Integer!> setExposureCompensationIndex(int);
method public com.google.common.util.concurrent.ListenableFuture<java.lang.Void!> setLinearZoom(@FloatRange(from=0.0f, to=1.0f) float);
method public com.google.common.util.concurrent.ListenableFuture<java.lang.Void!> setZoomRatio(float);
method public com.google.common.util.concurrent.ListenableFuture<androidx.camera.core.FocusMeteringResult!> startFocusAndMetering(androidx.camera.core.FocusMeteringAction);
@@ -27,6 +28,7 @@
}
public interface CameraInfo {
+ method @androidx.camera.core.ExperimentalExposureCompensation public androidx.camera.core.ExposureState getExposureState();
method public int getSensorRotationDegrees();
method public int getSensorRotationDegrees(int);
method public androidx.lifecycle.LiveData<java.lang.Integer!> getTorchState();
@@ -90,12 +92,22 @@
@experimental.Experimental @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) public @interface ExperimentalCustomizableThreads {
}
+ @experimental.Experimental @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) public @interface ExperimentalExposureCompensation {
+ }
+
@experimental.Experimental @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) public @interface ExperimentalGetImage {
}
@experimental.Experimental @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) public @interface ExperimentalUseCaseGroup {
}
+ @androidx.camera.core.ExperimentalExposureCompensation public interface ExposureState {
+ method public int getExposureCompensationIndex();
+ method public android.util.Range<java.lang.Integer!> getExposureCompensationRange();
+ method public android.util.Rational getExposureCompensationStep();
+ method public boolean isExposureCompensationSupported();
+ }
+
public interface ExtendableBuilder<T> {
method public T build();
}
@@ -269,6 +281,7 @@
method public int getTargetRotation();
method @UiThread public void setSurfaceProvider(java.util.concurrent.Executor, androidx.camera.core.Preview.SurfaceProvider?);
method @UiThread public void setSurfaceProvider(androidx.camera.core.Preview.SurfaceProvider?);
+ method @androidx.camera.core.ExperimentalUseCaseGroup public void setTargetRotation(int);
}
public static final class Preview.Builder implements androidx.camera.core.ExtendableBuilder<androidx.camera.core.Preview> {
@@ -291,9 +304,10 @@
public final class SurfaceRequest {
method public void addRequestCancellationListener(java.util.concurrent.Executor, Runnable);
- method @androidx.camera.core.ExperimentalUseCaseGroup public android.graphics.Rect getCropRect();
+ method @androidx.camera.core.ExperimentalUseCaseGroup public void clearTransformationInfoListener();
method public android.util.Size getResolution();
method public void provideSurface(android.view.Surface, java.util.concurrent.Executor, androidx.core.util.Consumer<androidx.camera.core.SurfaceRequest.Result!>);
+ method @androidx.camera.core.ExperimentalUseCaseGroup public void setTransformationInfoListener(java.util.concurrent.Executor, androidx.camera.core.SurfaceRequest.TransformationInfoListener);
method public boolean willNotProvideSurface();
}
@@ -307,6 +321,15 @@
field public static final int RESULT_WILL_NOT_PROVIDE_SURFACE = 4; // 0x4
}
+ @androidx.camera.core.ExperimentalUseCaseGroup @com.google.auto.value.AutoValue public abstract static class SurfaceRequest.TransformationInfo {
+ method public abstract android.graphics.Rect getCropRect();
+ method public abstract int getRotationDegrees();
+ }
+
+ @androidx.camera.core.ExperimentalUseCaseGroup public static interface SurfaceRequest.TransformationInfoListener {
+ method public void onTransformationInfoUpdate(androidx.camera.core.SurfaceRequest.TransformationInfo);
+ }
+
public class TorchState {
field public static final int OFF = 0; // 0x0
field public static final int ON = 1; // 0x1
diff --git a/camera/camera-core/src/androidTest/java/androidx/camera/core/SurfaceRequestTest.java b/camera/camera-core/src/androidTest/java/androidx/camera/core/SurfaceRequestTest.java
index 6278d58..e62068f 100644
--- a/camera/camera-core/src/androidTest/java/androidx/camera/core/SurfaceRequestTest.java
+++ b/camera/camera-core/src/androidTest/java/androidx/camera/core/SurfaceRequestTest.java
@@ -28,7 +28,6 @@
import android.view.Surface;
import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
import androidx.camera.core.impl.utils.executor.CameraXExecutors;
import androidx.camera.testing.fakes.FakeCamera;
import androidx.core.content.ContextCompat;
@@ -43,13 +42,15 @@
import java.util.ArrayList;
import java.util.List;
+import java.util.concurrent.atomic.AtomicReference;
@SmallTest
@RunWith(AndroidJUnit4.class)
public final class SurfaceRequestTest {
private static final Size FAKE_SIZE = new Size(0, 0);
- private static final Rect FAKE_VIEW_PORT_RECT = new Rect(0, 0, 640, 480);
+ private static final SurfaceRequest.TransformationInfo FAKE_INFO =
+ SurfaceRequest.TransformationInfo.of(new Rect(), 0);
private static final Consumer<SurfaceRequest.Result> NO_OP_RESULT_LISTENER = ignored -> {
};
private static final Surface MOCK_SURFACE = mock(Surface.class);
@@ -191,28 +192,38 @@
}
@Test
- public void createSurfaceRequestWithViewPort_cropRectIsSet() {
- assertThat(createNewRequest(FAKE_SIZE, FAKE_VIEW_PORT_RECT).getCropRect()).isEqualTo(
- FAKE_VIEW_PORT_RECT);
+ public void setListenerWhenTransformationAvailable_receivesImmediately() {
+ // Arrange.
+ SurfaceRequest request = createNewRequest(FAKE_SIZE);
+ request.updateTransformationInfo(FAKE_INFO);
+ AtomicReference<SurfaceRequest.TransformationInfo> infoReference = new AtomicReference<>();
+
+ // Act.
+ request.setTransformationInfoListener(CameraXExecutors.directExecutor(),
+ infoReference::set);
+
+ // Assert.
+ assertThat(infoReference.get()).isEqualTo(FAKE_INFO);
}
@Test
- public void createSurfaceRequestWithNullViewPort_cropRectIsFullSize() {
+ public void setListener_receivesCallbackWhenAvailable() {
// Arrange.
- Size size = new Size(200, 100);
+ SurfaceRequest request = createNewRequest(FAKE_SIZE);
+ AtomicReference<SurfaceRequest.TransformationInfo> infoReference = new AtomicReference<>();
+ request.setTransformationInfoListener(CameraXExecutors.directExecutor(),
+ infoReference::set);
+ assertThat(infoReference.get()).isNull();
+
+ // Act.
+ request.updateTransformationInfo(FAKE_INFO);
// Assert.
- assertThat(createNewRequest(size, null).getCropRect()).isEqualTo(
- new Rect(0, 0, size.getWidth(), size.getHeight()));
+ assertThat(infoReference.get()).isEqualTo(FAKE_INFO);
}
private SurfaceRequest createNewRequest(@NonNull Size size) {
- return createNewRequest(size, FAKE_VIEW_PORT_RECT);
- }
-
- private SurfaceRequest createNewRequest(@NonNull Size size, @Nullable Rect viewPortRect) {
- SurfaceRequest request = new SurfaceRequest(size, new FakeCamera(),
- viewPortRect);
+ SurfaceRequest request = new SurfaceRequest(size, new FakeCamera(), false);
mSurfaceRequests.add(request);
return request;
}
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 dba659f..e9df24a 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
@@ -150,6 +150,36 @@
ListenableFuture<Void> setLinearZoom(@FloatRange(from = 0f, to = 1f) float linearZoom);
/**
+ * Set the exposure compensation value for the camera.
+ *
+ * <p>Only one {@link #setExposureCompensationIndex} is allowed to run at the same time. If
+ * multiple {@link #setExposureCompensationIndex} are executed in a row, only the latest one
+ * setting will be kept in the camera. The other actions will be cancelled and the
+ * ListenableFuture will fail with the {@link OperationCanceledException}. After all the
+ * previous actions is cancelled, the camera device will adjust the brightness according to
+ * the latest setting.
+ *
+ * @param value the exposure compensation value to set on the camera which must be within
+ * the range of ExposureState#getExposureCompensationRange(). If the exposure
+ * compensation value is not in the range defined above, the returned
+ * {@link ListenableFuture} will fail with {@link IllegalArgumentException} and
+ * the value from ExposureState#getExposureCompensationIndex will not change.
+ * @return a {@link ListenableFuture} which is finished when the camera reaches the newly
+ * requested exposure target. Cancellation of this future is a no-op. The result of the
+ * ListenableFuture is the new target exposure value, or cancelled with the following
+ * exceptions,
+ * <ul>
+ * <li>{@link OperationCanceledException} when the camera is closed or a
+ * new {@link #setExposureCompensationIndex} is called.
+ * <li>{@link IllegalArgumentException} while the exposure compensation value to ranging
+ * within {@link ExposureState#getExposureCompensationRange}.
+ * </ul>
+ */
+ @NonNull
+ @ExperimentalExposureCompensation
+ ListenableFuture<Integer> setExposureCompensationIndex(int value);
+
+ /**
* An exception representing a failure that the operation is canceled which might be caused by
* a new value is set or camera is closed.
*
diff --git a/camera/camera-core/src/main/java/androidx/camera/core/CameraInfo.java b/camera/camera-core/src/main/java/androidx/camera/core/CameraInfo.java
index 29fd418..4ae94f0 100644
--- a/camera/camera-core/src/main/java/androidx/camera/core/CameraInfo.java
+++ b/camera/camera-core/src/main/java/androidx/camera/core/CameraInfo.java
@@ -133,6 +133,15 @@
LiveData<ZoomState> getZoomState();
/**
+ * Returns a {@link ExposureState}.
+ *
+ * <p>The {@link ExposureState} contains the current exposure related information.
+ */
+ @NonNull
+ @ExperimentalExposureCompensation
+ ExposureState getExposureState();
+
+ /**
* Returns the implementation type of the camera, this depends on the {@link CameraXConfig}
* used in the initialization of CameraX.
*
diff --git a/camera/camera-core/src/main/java/androidx/camera/core/ExperimentalExposureCompensation.java b/camera/camera-core/src/main/java/androidx/camera/core/ExperimentalExposureCompensation.java
new file mode 100644
index 0000000..b7c7ce4
--- /dev/null
+++ b/camera/camera-core/src/main/java/androidx/camera/core/ExperimentalExposureCompensation.java
@@ -0,0 +1,37 @@
+/*
+ * Copyright 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.camera.core;
+
+
+import static java.lang.annotation.RetentionPolicy.CLASS;
+
+import androidx.annotation.experimental.Experimental;
+
+import java.lang.annotation.Retention;
+
+/**
+ * Denotes that the annotated method uses the experimental ExposureCompensation APIs that can
+ * control the exposure compensation of the camera.
+ *
+ * <p>The feature allow the user to control the exposure compensation of the camera, it includes a
+ * setter in {@link androidx.camera.core.CameraControl} and a getter in
+ * {@link androidx.camera.core.CameraInfo}.
+ */
+@Retention(CLASS)
+@Experimental
+public @interface ExperimentalExposureCompensation {
+}
diff --git a/camera/camera-core/src/main/java/androidx/camera/core/ExposureState.java b/camera/camera-core/src/main/java/androidx/camera/core/ExposureState.java
new file mode 100644
index 0000000..919f708
--- /dev/null
+++ b/camera/camera-core/src/main/java/androidx/camera/core/ExposureState.java
@@ -0,0 +1,86 @@
+/*
+ * Copyright 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.camera.core;
+
+import android.util.Range;
+import android.util.Rational;
+
+import androidx.annotation.NonNull;
+
+/**
+ * An interface which contains the camera exposure related information.
+ *
+ * <p>Applications can retrieve an instance via {@link CameraInfo#getExposureState()}.
+ */
+@ExperimentalExposureCompensation
+public interface ExposureState {
+
+ /**
+ * Get the current exposure compensation index.
+ *
+ * <p>The exposure value (EV) is the compensation index multiplied by the step value
+ * which is given by {@link #getExposureCompensationStep()}. Increasing the compensation
+ * index by using the {@link CameraControl#setExposureCompensationIndex} will increase
+ * exposure making the capture result brighter, decreasing the value making it darker.
+ * <p>For example, if the exposure value (EV) step size is 0.333, set the exposure compensation
+ * index value '6' will mean an exposure compensation of +2 EV; -3 will mean an exposure
+ * compensation of -1 EV.
+ * <p>The exposure value resets to default when there is no {@link UseCase} associated with
+ * the camera. For example, unbind all use cases from the camera or when the lifecycle
+ * changed that all the use case stopping data from the camera.
+ *
+ * @return The current exposure compensation index. If {@link
+ * #isExposureCompensationSupported()} is false, always return 0.
+ * @see CameraControl#setExposureCompensationIndex
+ */
+ int getExposureCompensationIndex();
+
+ /**
+ * Get the maximum and minimum exposure compensation values for
+ * {@link CameraControl#setExposureCompensationIndex}
+ *
+ * <p>The actual exposure value (EV) range that supported by the camera can be calculated by
+ * multiplying the {@link #getExposureCompensationStep()} with the maximum and minimum values:
+ * <p><code>Min.exposure compensation * {@link #getExposureCompensationStep()} <= minimum
+ * supported EV</code>
+ * <p><code>Max.exposure compensation * {@link #getExposureCompensationStep()} >= maximum
+ * supported EV</code>
+ *
+ * @return the maximum and minimum exposure compensation values range. If {@link
+ * #isExposureCompensationSupported()} is false, return Range [0,0].
+ * @see android.hardware.camera2.CameraCharacteristics#CONTROL_AE_COMPENSATION_RANGE
+ */
+ @NonNull
+ Range<Integer> getExposureCompensationRange();
+
+ /**
+ * Get the smallest step by which the exposure compensation can be changed.
+ *
+ * @return The exposure compensation step. If {@link
+ * #isExposureCompensationSupported()} is false, return {@link Rational#ZERO}.
+ * @see android.hardware.camera2.CameraCharacteristics#CONTROL_AE_COMPENSATION_STEP
+ */
+ @NonNull
+ Rational getExposureCompensationStep();
+
+ /**
+ * Whether exposure compensation is supported for this camera.
+ *
+ * @return true if exposure compensation is supported for this camera.
+ */
+ boolean isExposureCompensationSupported();
+}
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 9f43630..1d7181af 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
@@ -37,6 +37,7 @@
import static androidx.camera.core.impl.UseCaseConfig.OPTION_CAMERA_SELECTOR;
import android.graphics.ImageFormat;
+import android.graphics.Rect;
import android.graphics.SurfaceTexture;
import android.media.ImageReader;
import android.media.MediaCodec;
@@ -56,8 +57,10 @@
import androidx.annotation.RestrictTo.Scope;
import androidx.annotation.UiThread;
import androidx.annotation.VisibleForTesting;
+import androidx.annotation.experimental.UseExperimental;
import androidx.camera.core.impl.CameraCaptureCallback;
import androidx.camera.core.impl.CameraCaptureResult;
+import androidx.camera.core.impl.CameraInternal;
import androidx.camera.core.impl.CaptureConfig;
import androidx.camera.core.impl.CaptureProcessor;
import androidx.camera.core.impl.CaptureStage;
@@ -78,7 +81,6 @@
import androidx.camera.core.internal.TargetConfig;
import androidx.camera.core.internal.ThreadConfig;
import androidx.core.util.Consumer;
-import androidx.core.util.Preconditions;
import java.util.List;
import java.util.UUID;
@@ -150,11 +152,16 @@
private DeferrableSurface mSessionDeferrableSurface;
- // SurfaceRequest created by the pipeline but hasn't been sent because SurfaceProvider is not
- // set. Null if there is no pending SurfaceRequest.
@VisibleForTesting
@Nullable
- SurfaceRequest mPendingSurfaceRequest;
+ SurfaceRequest mCurrentSurfaceRequest;
+ // Flag indicates that there is a SurfaceRequest created by Preview but hasn't sent to the
+ // caller.
+ private boolean mHasUnsentSurfaceRequest = false;
+ // The attached surface size. Same as getAttachedSurfaceResolution() but is available during
+ // createPipeline().
+ @Nullable
+ private Size mSurfaceSize;
/**
* Creates a new preview use case from the given configuration.
@@ -168,6 +175,7 @@
}
@SuppressWarnings("WeakerAccess") /* synthetic accessor */
+ @UseExperimental(markerClass = ExperimentalUseCaseGroup.class)
SessionConfig.Builder createPipeline(@NonNull String cameraId, @NonNull PreviewConfig config,
@NonNull Size resolution) {
Threads.checkMainThread();
@@ -180,11 +188,13 @@
}
final SurfaceRequest surfaceRequest = new SurfaceRequest(resolution, getCamera(),
- getViewPortCropRect());
+ captureProcessor != null);
+ mCurrentSurfaceRequest = surfaceRequest;
- if (!sendSurfaceRequest(surfaceRequest)) {
- // SurfaceRequest can't be sent. Save and send at a later time.
- mPendingSurfaceRequest = surfaceRequest;
+ if (sendSurfaceRequestIfReady()) {
+ sendTransformationInfoIfReady();
+ } else {
+ mHasUnsentSurfaceRequest = true;
}
if (captureProcessor != null) {
@@ -272,12 +282,44 @@
* @param targetRotation Target rotation of the output image, expressed as one of
* {@link Surface#ROTATION_0}, {@link Surface#ROTATION_90},
* {@link Surface#ROTATION_180}, or {@link Surface#ROTATION_270}.
- * @hide
* @see Preview.Builder#setTargetRotation(int)
*/
- @RestrictTo(Scope.LIBRARY_GROUP)
+ @ExperimentalUseCaseGroup
public void setTargetRotation(@ImageOutputConfig.RotationValue int targetRotation) {
- setTargetRotationInternal(targetRotation);
+ if (setTargetRotationInternal(targetRotation)) {
+ sendTransformationInfoIfReady();
+ }
+ }
+
+ @ExperimentalUseCaseGroup
+ private void sendTransformationInfoIfReady() {
+ // TODO(b/159659392): only send transformation after CameraCaptureCallback
+ // .onCaptureCompleted is called.
+ CameraInternal cameraInternal = getCamera();
+ SurfaceProvider surfaceProvider = mSurfaceProvider;
+ Rect cropRect = getCropRect(mSurfaceSize);
+ SurfaceRequest surfaceRequest = mCurrentSurfaceRequest;
+ if (cameraInternal != null && surfaceProvider != null && cropRect != null) {
+ surfaceRequest.updateTransformationInfo(SurfaceRequest.TransformationInfo.of(cropRect,
+ getRelativeRotation(cameraInternal)));
+ }
+ }
+
+ /**
+ * Gets the crop rect for {@link Preview}.
+ *
+ * <p> Fall back to the full {@link Surface} rect if {@link ViewPort} crop rect is not
+ * available. Returns null if no valid crop rect. This could happen if the {@link Preview} is
+ * not attached to a camera.
+ */
+ @Nullable
+ private Rect getCropRect(@Nullable Size surfaceResolution) {
+ if (getViewPortCropRect() != null) {
+ return getViewPortCropRect();
+ } else if (surfaceResolution != null) {
+ return new Rect(0, 0, surfaceResolution.getWidth(), surfaceResolution.getHeight());
+ }
+ return null;
}
/**
@@ -293,6 +335,7 @@
* {@link #setSurfaceProvider(SurfaceProvider)}.
*/
@UiThread
+ @UseExperimental(markerClass = ExperimentalUseCaseGroup.class)
public void setSurfaceProvider(@NonNull Executor executor,
@Nullable SurfaceProvider surfaceProvider) {
Threads.checkMainThread();
@@ -305,9 +348,11 @@
mSurfaceProviderExecutor = executor;
notifyActive();
- if (mPendingSurfaceRequest != null) {
- sendSurfaceRequest(mPendingSurfaceRequest);
- mPendingSurfaceRequest = null;
+ if (mHasUnsentSurfaceRequest) {
+ if (sendSurfaceRequestIfReady()) {
+ sendTransformationInfoIfReady();
+ mHasUnsentSurfaceRequest = false;
+ }
} else {
// No pending SurfaceRequest. It could be a previous request has already been
// sent, which means the caller wants to replace the Surface. Or, it could be the
@@ -323,10 +368,10 @@
}
}
- private boolean sendSurfaceRequest(@NonNull SurfaceRequest surfaceRequest) {
- Preconditions.checkNotNull(surfaceRequest);
+ private boolean sendSurfaceRequestIfReady() {
+ final SurfaceRequest surfaceRequest = mCurrentSurfaceRequest;
final SurfaceProvider surfaceProvider = mSurfaceProvider;
- if (surfaceProvider != null) {
+ if (surfaceProvider != null && surfaceRequest != null) {
mSurfaceProviderExecutor.execute(
() -> surfaceProvider.onSurfaceRequested(surfaceRequest));
return true;
@@ -437,7 +482,7 @@
@Override
public void onDestroy() {
mSurfaceProvider = null;
- mPendingSurfaceRequest = null;
+ mCurrentSurfaceRequest = null;
}
/**
@@ -449,6 +494,7 @@
@RestrictTo(Scope.LIBRARY_GROUP)
@NonNull
protected Size onSuggestedResolutionUpdated(@NonNull Size suggestedResolution) {
+ mSurfaceSize = suggestedResolution;
updateConfigAndOutput(getCameraId(), (PreviewConfig) getUseCaseConfig(),
suggestedResolution);
return suggestedResolution;
diff --git a/camera/camera-core/src/main/java/androidx/camera/core/SurfaceRequest.java b/camera/camera-core/src/main/java/androidx/camera/core/SurfaceRequest.java
index 2831786..46f251c 100644
--- a/camera/camera-core/src/main/java/androidx/camera/core/SurfaceRequest.java
+++ b/camera/camera-core/src/main/java/androidx/camera/core/SurfaceRequest.java
@@ -17,16 +17,21 @@
package androidx.camera.core;
import android.annotation.SuppressLint;
+import android.graphics.Matrix;
import android.graphics.Rect;
import android.graphics.SurfaceTexture;
+import android.hardware.camera2.CameraCharacteristics;
import android.util.Size;
import android.view.Surface;
+import android.view.SurfaceView;
+import android.view.TextureView;
import androidx.annotation.IntDef;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.RestrictTo;
import androidx.camera.core.impl.DeferrableSurface;
+import androidx.camera.core.impl.ImageOutputConfig;
import androidx.camera.core.impl.utils.executor.CameraXExecutors;
import androidx.camera.core.impl.utils.futures.FutureCallback;
import androidx.camera.core.impl.utils.futures.Futures;
@@ -56,7 +61,7 @@
private final Size mResolution;
private final Camera mCamera;
- private final Rect mViewPortRect;
+ private final boolean mRGBA8888Required;
// For the camera to retrieve the surface from the user
@SuppressWarnings("WeakerAccess") /*synthetic accessor */
@@ -73,6 +78,14 @@
private DeferrableSurface mInternalDeferrableSurface;
+ @Nullable
+ private TransformationInfo mTransformationInfo;
+ @Nullable
+ private TransformationInfoListener mTransformationInfoListener;
+ // Executor for calling TransformationUpdateListener.
+ @Nullable
+ private Executor mTransformationInfoExecutor;
+
/**
* Creates a new surface request with the given resolution, the {@link Camera} and the crop
* rect.
@@ -83,13 +96,11 @@
public SurfaceRequest(
@NonNull Size resolution,
@NonNull Camera camera,
- @Nullable Rect viewPortRect) {
+ boolean isRGBA8888Required) {
super();
mResolution = resolution;
mCamera = camera;
- // Use full surface rect if viewPortRect is null.
- mViewPortRect = viewPortRect != null ? viewPortRect : new Rect(0, 0, resolution.getWidth(),
- resolution.getHeight());
+ mRGBA8888Required = isRGBA8888Required;
// To ensure concurrency and ordering, operations are chained. Completion can only be
// triggered externally by the top-level completer (mSurfaceCompleter). The other future
@@ -243,22 +254,15 @@
}
/**
- * Returns the crop rect rectangle.
+ * Returns whether a surface of RGBA_8888 pixel format is required.
*
- * <p> The returned value dictates how the {@link Surface} provided in
- * {@link #provideSurface(Surface, Executor, Consumer)} should be displayed. The crop
- * rectangle specifies the region of valid pixels in the buffer, using coordinates from (0,
- * 0) to the (width, height) of {@link #getResolution()}. The caller should arrange the UI so
- * that only the valid region is visible to app users.
+ * @return true if a surface of RGBA_8888 pixel format is required.
*
- * <p> If {@link Preview} is configured with a {@link ViewPort}, this value is calculated
- * based on the configuration of {@link ViewPort}; if not, it returns the full rect of the
- * buffer.
+ * @hide
*/
- @ExperimentalUseCaseGroup
- @NonNull
- public Rect getCropRect() {
- return mViewPortRect;
+ @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
+ public boolean isRGBA8888Required() {
+ return mRGBA8888Required;
}
/**
@@ -396,6 +400,57 @@
}
/**
+ * Updates the {@link TransformationInfo} associated with this {@link SurfaceRequest}.
+ *
+ * @hide
+ */
+ @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
+ @ExperimentalUseCaseGroup
+ void updateTransformationInfo(@NonNull TransformationInfo transformationInfo) {
+ mTransformationInfo = transformationInfo;
+ TransformationInfoListener listener = mTransformationInfoListener;
+ if (listener != null) {
+ mTransformationInfoExecutor.execute(
+ () -> listener.onTransformationInfoUpdate(
+ transformationInfo));
+
+ }
+ }
+
+ /**
+ * Sets a listener to receive updates on transformation info.
+ *
+ * <p> Sets a listener to receive the transformation info associated with this
+ * {@link SurfaceRequest} when it changes or becomes available. The listener is called
+ * immediately if transformation info is available at the time of setting.
+ *
+ * @param executor The executor used to notify the listener.
+ * @param listener the listener which will be called when transformation info changes.
+ * @see TransformationInfoListener
+ * @see TransformationInfo
+ */
+ @ExperimentalUseCaseGroup
+ public void setTransformationInfoListener(@NonNull Executor executor,
+ @NonNull TransformationInfoListener listener) {
+ mTransformationInfoListener = listener;
+ mTransformationInfoExecutor = executor;
+ TransformationInfo transformationInfo = mTransformationInfo;
+ if (transformationInfo != null) {
+ executor.execute(() -> listener.onTransformationInfoUpdate(
+ transformationInfo));
+ }
+ }
+
+ /**
+ * Clears the {@link TransformationInfoListener} set via {@link #setTransformationInfoListener}.
+ */
+ @ExperimentalUseCaseGroup
+ public void clearTransformationInfoListener() {
+ mTransformationInfoListener = null;
+ mTransformationInfoExecutor = null;
+ }
+
+ /**
* An exception used to signal that the camera has cancelled a request for a {@link Surface}.
*
* <p>This may be set on the {@link ListenableFuture} returned by
@@ -412,6 +467,31 @@
}
/**
+ * Listener that receives updates of the {@link TransformationInfo} associated with the
+ * {@link SurfaceRequest}.
+ */
+ @ExperimentalUseCaseGroup
+ public interface TransformationInfoListener {
+
+ /**
+ * Called when the {@link TransformationInfo} is updated.
+ *
+ * <p> This is called when the transformation info becomes available or is updated.
+ * The rotation degrees is updated after calling {@link Preview#setTargetRotation}, and the
+ * crop rect is updated after changing the {@link ViewPort} associated with the
+ * {@link Preview}.
+ *
+ * @param transformationInfo apply the transformation info to transform {@link Preview}
+ * @see TransformationInfo
+ * @see Preview#setTargetRotation(int)
+ * @see Preview.Builder#setTargetRotation(int)
+ * @see CameraCharacteristics#SENSOR_ORIENTATION
+ * @see ViewPort
+ */
+ void onTransformationInfoUpdate(@NonNull TransformationInfo transformationInfo);
+ }
+
+ /**
* Result of providing a surface to a {@link SurfaceRequest} via
* {@link #provideSurface(Surface, Executor, Consumer)}.
*/
@@ -530,4 +610,100 @@
Result() {
}
}
+
+ /**
+ * Transformation associated the preview output.
+ *
+ * <p> The {@link TransformationInfo} can be used transform the {@link Surface} provided via
+ * {@link SurfaceRequest#provideSurface}. The info is based on the camera sensor rotation,
+ * preview target rotation and the {@link ViewPort} associated with the {@link Preview}. The
+ * application of the info depends on the source of the {@link Surface}. For detailed example,
+ * please check out the source code of PreviewView in androidx.camera.view artifact.
+ *
+ * <p> The info is also needed to transform coordinates across use cases. In a face detection
+ * example, one common scenario is running a face detection algorithm against a
+ * {@link ImageAnalysis} use case, and highlight the detected face in the preview. Below is
+ * a code sample to get the transformation {@link Matrix} based on the {@link ImageProxy} from
+ * {@link ImageAnalysis} and the {@link TransformationInfo} from {@link Preview}:
+ *
+ * <pre><code>
+ * // Get rotation transformation.
+ * val transformation = Matrix()
+ * transformation.setRotate(info.getRotationDegrees())
+ *
+ * // Get rotated crop rect and cropping transformation.
+ * val rotatedRect = new RectF()
+ * rotation.mapRect(rotatedRect, RectF(imageProxy.getCropRect()))
+ * rotatedRect.sort()
+ * val cropTransformation = Matrix()
+ * cropTransformation.setRectToRect(
+ * RectF(imageProxy.getCropRect()), rotatedRect, ScaleToFit.FILL)
+ *
+ * // Concatenate the rotation and cropping transformations.
+ * transformation.postConcat(cropTransformation)
+ * </code></pre>
+ *
+ * @see Preview#setTargetRotation(int)
+ * @see Preview.Builder#setTargetRotation(int)
+ * @see CameraCharacteristics#SENSOR_ORIENTATION
+ * @see ViewPort
+ */
+ @ExperimentalUseCaseGroup
+ @AutoValue
+ public abstract static class TransformationInfo {
+
+ /**
+ * Returns the crop rect rectangle.
+ *
+ * <p> The returned value dictates how the {@link Surface} provided in
+ * {@link SurfaceRequest#provideSurface} should be displayed. The crop
+ * rectangle specifies the region of valid pixels in the buffer, using coordinates from (0,
+ * 0) to the (width, height) of {@link SurfaceRequest#getResolution}. The caller should
+ * arrange the UI so that only the valid region is visible to app users.
+ *
+ * <p> If {@link Preview} is configured with a {@link ViewPort}, this value is calculated
+ * based on the configuration of {@link ViewPort}; if not, it returns the full rect of the
+ * buffer. For code sample on how to apply the crop rect, please see {@link ViewPort#FIT}.
+ *
+ * @see ViewPort
+ */
+ @NonNull
+ public abstract Rect getCropRect();
+
+ /**
+ * Returns the rotation needed to transform the output from sensor to the target
+ * rotation.
+ *
+ * <p> This is a clockwise rotation in degrees that needs to be applied to the sensor
+ * buffer. The rotation will be determined by {@link CameraCharacteristics},
+ * {@link Preview#setTargetRotation(int)} and
+ * {@link Preview.Builder#setTargetRotation(int)}. This value is useful for transforming
+ * coordinates across use cases.
+ *
+ * <p> This value is most useful for transforming coordinates across use cases, e.g. in
+ * preview, highlighting a pattern detected in image analysis. For correcting
+ * the preview itself, usually the source of the {@link Surface} handles the rotation
+ * without needing this value. For {@link SurfaceView}, it automatically corrects the
+ * preview to match the display rotation. For {@link TextureView}, the only additional
+ * rotation needed is the display rotation. For detailed example, please check out the
+ * source code of PreviewView in androidx.camera .view artifact.
+ *
+ * @return The rotation in degrees which will be a value in {0, 90, 180, 270}.
+ * @see Preview#setTargetRotation(int)
+ * @see Preview#getTargetRotation()
+ * @see ViewPort
+ */
+ @ImageOutputConfig.RotationDegreesValue
+ public abstract int getRotationDegrees();
+
+ @NonNull
+ static TransformationInfo of(@NonNull Rect cropRect,
+ @ImageOutputConfig.RotationDegreesValue int rotationDegrees) {
+ return new AutoValue_SurfaceRequest_TransformationInfo(cropRect, rotationDegrees);
+ }
+
+ // Hides public constructor.
+ TransformationInfo() {
+ }
+ }
}
diff --git a/camera/camera-core/src/main/java/androidx/camera/core/ViewPort.java b/camera/camera-core/src/main/java/androidx/camera/core/ViewPort.java
index 3142a35..bcd177e 100644
--- a/camera/camera-core/src/main/java/androidx/camera/core/ViewPort.java
+++ b/camera/camera-core/src/main/java/androidx/camera/core/ViewPort.java
@@ -41,10 +41,10 @@
*
* <p> For {@link ImageAnalysis} and in-memory {@link ImageCapture}, the output crop rect is
* {@link ImageProxy#getCropRect()}; for on-disk {@link ImageCapture}, the image is cropped before
- * saving; for {@link Preview}, the crop rect is {@link SurfaceRequest#getCropRect()}. Caller
- * should transform the output in a way that only the area defined by the crop rect is visible
- * to end users. Once the crop rect is applied, all the use cases will produce the same image
- * with possibly different resolutions.
+ * saving; for {@link Preview}, the crop rect is
+ * {@link SurfaceRequest.TransformationInfo#getCropRect()}. Caller should transform the output in
+ * a way that only the area defined by the crop rect is visible to end users. Once the crop rect
+ * is applied, all the use cases will produce the same image with possibly different resolutions.
*/
@ExperimentalUseCaseGroup
public final class ViewPort {
diff --git a/camera/camera-core/src/main/java/androidx/camera/core/impl/CameraControlInternal.java b/camera/camera-core/src/main/java/androidx/camera/core/impl/CameraControlInternal.java
index 8eacc77..86f318c 100644
--- a/camera/camera-core/src/main/java/androidx/camera/core/impl/CameraControlInternal.java
+++ b/camera/camera-core/src/main/java/androidx/camera/core/impl/CameraControlInternal.java
@@ -23,6 +23,7 @@
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.camera.core.CameraControl;
+import androidx.camera.core.ExperimentalExposureCompensation;
import androidx.camera.core.FocusMeteringAction;
import androidx.camera.core.FocusMeteringResult;
import androidx.camera.core.ImageCapture.FlashMode;
@@ -83,6 +84,18 @@
void cancelAfAeTrigger(boolean cancelAfTrigger, boolean cancelAePrecaptureTrigger);
/**
+ * Set a exposure compensation to the camera
+ *
+ * @param exposure the exposure compensation value to set
+ * @return a ListenableFuture which is completed when the new exposure compensation reach the
+ * target.
+ */
+ @NonNull
+ @Override
+ @ExperimentalExposureCompensation
+ ListenableFuture<Integer> setExposureCompensationIndex(int exposure);
+
+ /**
* Performs capture requests.
*/
void submitCaptureRequests(@NonNull List<CaptureConfig> captureConfigs);
@@ -131,6 +144,13 @@
}
+ @NonNull
+ @Override
+ @ExperimentalExposureCompensation
+ public ListenableFuture<Integer> setExposureCompensationIndex(int exposure) {
+ return Futures.immediateFuture(0);
+ }
+
@Override
public void submitCaptureRequests(@NonNull List<CaptureConfig> captureConfigs) {
}
diff --git a/camera/camera-core/src/main/java/androidx/camera/core/impl/ImageOutputConfig.java b/camera/camera-core/src/main/java/androidx/camera/core/impl/ImageOutputConfig.java
index dc9270b..a78c0a7 100644
--- a/camera/camera-core/src/main/java/androidx/camera/core/impl/ImageOutputConfig.java
+++ b/camera/camera-core/src/main/java/androidx/camera/core/impl/ImageOutputConfig.java
@@ -25,6 +25,7 @@
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.camera.core.AspectRatio;
+import androidx.camera.core.ExperimentalUseCaseGroup;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@@ -319,4 +320,13 @@
@Retention(RetentionPolicy.SOURCE)
@interface RotationValue {
}
+
+ /**
+ * Valid integer rotation degrees values.
+ */
+ @IntDef({0, 90, 180, 270})
+ @Retention(RetentionPolicy.SOURCE)
+ @ExperimentalUseCaseGroup
+ @interface RotationDegreesValue {
+ }
}
diff --git a/camera/camera-core/src/test/java/androidx/camera/core/PreviewTest.kt b/camera/camera-core/src/test/java/androidx/camera/core/PreviewTest.kt
index b478e33..0dd8ddc 100644
--- a/camera/camera-core/src/test/java/androidx/camera/core/PreviewTest.kt
+++ b/camera/camera-core/src/test/java/androidx/camera/core/PreviewTest.kt
@@ -25,6 +25,7 @@
import android.view.Surface
import androidx.camera.core.impl.CameraFactory
import androidx.camera.core.impl.CameraThreadConfig
+import androidx.camera.core.impl.utils.executor.CameraXExecutors
import androidx.camera.testing.CameraUtil
import androidx.camera.testing.fakes.FakeAppConfig
import androidx.camera.testing.fakes.FakeCamera
@@ -51,7 +52,9 @@
@SmallTest
@RunWith(RobolectricTestRunner::class)
@DoNotInstrument
-@Config(minSdk = Build.VERSION_CODES.LOLLIPOP, shadows = [ShadowCameraX::class])
+@Config(
+ minSdk = Build.VERSION_CODES.LOLLIPOP, shadows = [ShadowCameraX::class]
+)
class PreviewTest {
@Before
@@ -79,15 +82,15 @@
}
@Test
- fun viewPortCropSize() {
- val expectedSurfaceRequest = bindToLifecycleAndGetSurfaceRequest(
+ fun viewPortSet_cropRectIsBasedOnViewPort() {
+ val transformationInfo = bindToLifecycleAndGetTransformationInfo(
ViewPort.Builder(Rational(1, 1), Surface.ROTATION_0).build()
)
// The expected value is based on fitting the 1:1 view port into a rect with the size of
// FakeCameraDeviceSurfaceManager.MAX_OUTPUT_SIZE.
val expectedPadding = (FakeCameraDeviceSurfaceManager.MAX_OUTPUT_SIZE.width -
FakeCameraDeviceSurfaceManager.MAX_OUTPUT_SIZE.height) / 2
- assertThat(expectedSurfaceRequest.cropRect).isEqualTo(
+ assertThat(transformationInfo.cropRect).isEqualTo(
Rect(
expectedPadding,
0,
@@ -98,6 +101,22 @@
}
@Test
+ fun viewPortNotSet_cropRectIsFullSurface() {
+ val transformationInfo = bindToLifecycleAndGetTransformationInfo(
+ null
+ )
+
+ assertThat(transformationInfo.cropRect).isEqualTo(
+ Rect(
+ 0,
+ 0,
+ FakeCameraDeviceSurfaceManager.MAX_OUTPUT_SIZE.width,
+ FakeCameraDeviceSurfaceManager.MAX_OUTPUT_SIZE.height
+ )
+ )
+ }
+
+ @Test
fun surfaceRequestSize_isSurfaceSize() {
assertThat(bindToLifecycleAndGetSurfaceRequest().resolution).isEqualTo(
Size(
@@ -120,7 +139,7 @@
}
@Test
- fun setSurfaceProviderAfterAttachment_receivesSurfaceRequest() {
+ fun setSurfaceProviderAfterAttachment_receivesSurfaceProviderCallbacks() {
// Arrange: attach Preview without a SurfaceProvider.
val preview = Preview.Builder().setTargetRotation(Surface.ROTATION_0).build()
val cameraUseCaseAdapter = CameraUtil.getCameraUseCaseAdapter(
@@ -129,32 +148,64 @@
)
cameraUseCaseAdapter.addUseCases(Collections.singleton<UseCase>(preview))
- // Unsent pending SurfaceRequest created by pipeline.
- val pendingSurfaceRequest = preview.mPendingSurfaceRequest
+ // Get pending SurfaceRequest created by pipeline.
+ val pendingSurfaceRequest = preview.mCurrentSurfaceRequest
var receivedSurfaceRequest: SurfaceRequest? = null
+ var receivedTransformationInfo: SurfaceRequest.TransformationInfo? = null
// Act: set a SurfaceProvider after attachment.
- preview.setSurfaceProvider { receivedSurfaceRequest = it }
+ preview.setSurfaceProvider { request ->
+ request.setTransformationInfoListener(
+ CameraXExecutors.directExecutor(),
+ SurfaceRequest.TransformationInfoListener {
+ receivedTransformationInfo = it
+ })
+ receivedSurfaceRequest = request
+ }
shadowOf(getMainLooper()).idle()
- // Assert: received a SurfaceRequest.
+
+ // Assert: received SurfaceRequest is the pending SurfaceRequest.
assertThat(receivedSurfaceRequest).isSameInstanceAs(pendingSurfaceRequest)
+ assertThat(receivedTransformationInfo).isNotNull()
// Act: set a different SurfaceProvider.
- preview.setSurfaceProvider { receivedSurfaceRequest = it }
+ preview.setSurfaceProvider { request ->
+ request.setTransformationInfoListener(
+ CameraXExecutors.directExecutor(),
+ SurfaceRequest.TransformationInfoListener {
+ receivedTransformationInfo = it
+ })
+ receivedSurfaceRequest = request
+ }
shadowOf(getMainLooper()).idle()
+
// Assert: received a different SurfaceRequest.
assertThat(receivedSurfaceRequest).isNotSameInstanceAs(pendingSurfaceRequest)
}
private fun bindToLifecycleAndGetSurfaceRequest(): SurfaceRequest {
- return bindToLifecycleAndGetSurfaceRequest(null)
+ return bindToLifecycleAndGetResult(null).first
}
- private fun bindToLifecycleAndGetSurfaceRequest(viewPort: ViewPort?): SurfaceRequest {
+ private fun bindToLifecycleAndGetTransformationInfo(viewPort: ViewPort?):
+ SurfaceRequest.TransformationInfo {
+ return bindToLifecycleAndGetResult(viewPort).second
+ }
+
+ private fun bindToLifecycleAndGetResult(viewPort: ViewPort?): Pair<SurfaceRequest,
+ SurfaceRequest.TransformationInfo> {
// Arrange.
val preview = Preview.Builder().setTargetRotation(Surface.ROTATION_0).build()
var surfaceRequest: SurfaceRequest? = null
- preview.setSurfaceProvider { surfaceRequest = it }
+ var transformationInfo: SurfaceRequest.TransformationInfo? = null
+ preview.setSurfaceProvider { request ->
+ request.setTransformationInfoListener(
+ CameraXExecutors.directExecutor(),
+ SurfaceRequest.TransformationInfoListener {
+ transformationInfo = it
+ })
+ surfaceRequest = request
+ }
// Act.
val cameraUseCaseAdapter = CameraUtil.getCameraUseCaseAdapter(
@@ -165,6 +216,6 @@
cameraUseCaseAdapter.setViewPort(viewPort)
cameraUseCaseAdapter.addUseCases(Collections.singleton<UseCase>(preview))
InstrumentationRegistry.getInstrumentation().waitForIdleSync()
- return surfaceRequest!!
+ return Pair(surfaceRequest!!, transformationInfo!!)
}
}
\ No newline at end of file
diff --git a/camera/camera-testing/build.gradle b/camera/camera-testing/build.gradle
index 3a1620a..b9f6fbc 100644
--- a/camera/camera-testing/build.gradle
+++ b/camera/camera-testing/build.gradle
@@ -48,10 +48,28 @@
android {
defaultConfig {
minSdkVersion 21
+
+ externalNativeBuild {
+ cmake {
+ cppFlags "-std=c++17"
+ arguments "-DCMAKE_VERBOSE_MAKEFILE=ON"
+ }
+ }
}
// Use Robolectric 4.+
testOptions.unitTests.includeAndroidResources = true
+
+ externalNativeBuild {
+ cmake {
+ // AGP doesn't allow us to use project.buildDir (or subdirs) for CMake's generated
+ // build files (ninja build files, CMakeCache.txt, etc.). Use a staging directory that
+ // lives alongside the project's buildDir.
+ buildStagingDirectory "${project.buildDir}/../nativeBuildStaging"
+ path "src/main/cpp/CMakeLists.txt"
+ version "3.10.2"
+ }
+ }
}
androidx {
diff --git a/camera/camera-testing/src/main/cpp/CMakeLists.txt b/camera/camera-testing/src/main/cpp/CMakeLists.txt
new file mode 100644
index 0000000..d69267f
--- /dev/null
+++ b/camera/camera-testing/src/main/cpp/CMakeLists.txt
@@ -0,0 +1,28 @@
+#
+# Copyright (C) 2020 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may not
+# use this file except in compliance with the License. You may obtain a copy of
+# the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations under
+# the License.
+#
+
+cmake_minimum_required(VERSION 3.4.1)
+
+set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -Werror")
+
+add_library(
+ testing_surface_format_jni SHARED
+ jni_hooks.cpp
+ surface_format_jni.cpp)
+
+find_library(android-lib android)
+
+target_link_libraries(testing_surface_format_jni ${android-lib})
\ No newline at end of file
diff --git a/appsearch/appsearch/src/main/java/androidx/appsearch/impl/package-info.java b/camera/camera-testing/src/main/cpp/jni_hooks.cpp
similarity index 81%
rename from appsearch/appsearch/src/main/java/androidx/appsearch/impl/package-info.java
rename to camera/camera-testing/src/main/cpp/jni_hooks.cpp
index 68c3b98..cc1e31d 100644
--- a/appsearch/appsearch/src/main/java/androidx/appsearch/impl/package-info.java
+++ b/camera/camera-testing/src/main/cpp/jni_hooks.cpp
@@ -14,10 +14,10 @@
* limitations under the License.
*/
-/**
- * @hide
- */
-@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
-package androidx.appsearch.impl;
+#include <jni.h>
-import androidx.annotation.RestrictTo;
+extern "C" {
+JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM *jvm, void *reserved) {
+ return JNI_VERSION_1_6;
+}
+}
\ No newline at end of file
diff --git a/camera/camera-testing/src/main/cpp/surface_format_jni.cpp b/camera/camera-testing/src/main/cpp/surface_format_jni.cpp
new file mode 100644
index 0000000..7207b0e
--- /dev/null
+++ b/camera/camera-testing/src/main/cpp/surface_format_jni.cpp
@@ -0,0 +1,31 @@
+/*
+ * Copyright 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <android/native_window_jni.h>
+
+#include <cassert>
+
+extern "C" {
+
+JNIEXPORT jint JNICALL
+Java_androidx_camera_testing_SurfaceFormatUtil_nativeGetSurfaceFormat(JNIEnv *env, jclass clazz,
+ jobject jsurface) {
+ ANativeWindow *nativeWindow = ANativeWindow_fromSurface(env, jsurface);
+ assert(nativeWindow != nullptr);
+ return ANativeWindow_getFormat(nativeWindow);
+}
+
+} // extern "C"
diff --git a/camera/camera-testing/src/main/java/androidx/camera/testing/SurfaceFormatUtil.java b/camera/camera-testing/src/main/java/androidx/camera/testing/SurfaceFormatUtil.java
new file mode 100644
index 0000000..207d89803
--- /dev/null
+++ b/camera/camera-testing/src/main/java/androidx/camera/testing/SurfaceFormatUtil.java
@@ -0,0 +1,41 @@
+/*
+ * Copyright 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.camera.testing;
+
+import android.view.Surface;
+
+import androidx.annotation.Nullable;
+
+/**
+ * Surface format related utility functions.
+ */
+public class SurfaceFormatUtil {
+ private SurfaceFormatUtil() {}
+
+ /**
+ * Returns the surface pixel format.
+ */
+ public static int getSurfaceFormat(@Nullable Surface surface) {
+ return nativeGetSurfaceFormat(surface);
+ }
+
+ static {
+ System.loadLibrary("testing_surface_format_jni");
+ }
+
+ private static native int nativeGetSurfaceFormat(@Nullable Surface surface);
+}
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 ae9118a..4f50bb9 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
@@ -24,6 +24,7 @@
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
+import androidx.camera.core.ExperimentalExposureCompensation;
import androidx.camera.core.FocusMeteringAction;
import androidx.camera.core.FocusMeteringResult;
import androidx.camera.core.ImageCapture;
@@ -138,6 +139,13 @@
+ cancelAePrecaptureTrigger + ")");
}
+ @NonNull
+ @Override
+ @ExperimentalExposureCompensation
+ public ListenableFuture<Integer> setExposureCompensationIndex(int exposure) {
+ return Futures.immediateFuture(null);
+ }
+
@Override
public void submitCaptureRequests(@NonNull List<CaptureConfig> captureConfigs) {
mSubmittedCaptureRequests.addAll(captureConfigs);
diff --git a/camera/camera-testing/src/main/java/androidx/camera/testing/fakes/FakeCameraInfoInternal.java b/camera/camera-testing/src/main/java/androidx/camera/testing/fakes/FakeCameraInfoInternal.java
index d3d9803..19d3ab7 100644
--- a/camera/camera-testing/src/main/java/androidx/camera/testing/fakes/FakeCameraInfoInternal.java
+++ b/camera/camera-testing/src/main/java/androidx/camera/testing/fakes/FakeCameraInfoInternal.java
@@ -16,11 +16,15 @@
package androidx.camera.testing.fakes;
+import android.util.Range;
+import android.util.Rational;
import android.view.Surface;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.camera.core.CameraSelector;
+import androidx.camera.core.ExperimentalExposureCompensation;
+import androidx.camera.core.ExposureState;
import androidx.camera.core.TorchState;
import androidx.camera.core.ZoomState;
import androidx.camera.core.impl.CameraCaptureCallback;
@@ -119,6 +123,35 @@
@NonNull
@Override
+ @ExperimentalExposureCompensation
+ public ExposureState getExposureState() {
+ return new ExposureState() {
+ @Override
+ public int getExposureCompensationIndex() {
+ return 0;
+ }
+
+ @NonNull
+ @Override
+ public Range<Integer> getExposureCompensationRange() {
+ return Range.create(0, 0);
+ }
+
+ @NonNull
+ @Override
+ public Rational getExposureCompensationStep() {
+ return Rational.ZERO;
+ }
+
+ @Override
+ public boolean isExposureCompensationSupported() {
+ return true;
+ }
+ };
+ }
+
+ @NonNull
+ @Override
public String getImplementationType() {
return mImplementationType;
}
diff --git a/camera/camera-view/src/androidTest/java/androidx/camera/view/PreviewViewBitmapTest.java b/camera/camera-view/src/androidTest/java/androidx/camera/view/PreviewViewBitmapTest.java
index fa0576f..dc2454d 100644
--- a/camera/camera-view/src/androidTest/java/androidx/camera/view/PreviewViewBitmapTest.java
+++ b/camera/camera-view/src/androidTest/java/androidx/camera/view/PreviewViewBitmapTest.java
@@ -51,6 +51,7 @@
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Semaphore;
import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicReference;
@LargeTest
@RunWith(AndroidJUnit4.class)
@@ -252,12 +253,15 @@
@NonNull
private PreviewView setUpPreviewView(@NonNull PreviewView.ImplementationMode mode,
@NonNull PreviewView.ScaleType scaleType) {
- final Context context = ApplicationProvider.getApplicationContext();
- final PreviewView previewView = new PreviewView(context);
- previewView.setImplementationMode(mode);
- previewView.setScaleType(scaleType);
- runOnMainThread(() -> mActivityRule.getActivity().setContentView(previewView));
- return previewView;
+ AtomicReference<PreviewView> previewViewAtomicReference = new AtomicReference<>();
+ runOnMainThread(() -> {
+ PreviewView previewView = new PreviewView(ApplicationProvider.getApplicationContext());
+ previewView.setImplementationMode(mode);
+ previewView.setScaleType(scaleType);
+ mActivityRule.getActivity().setContentView(previewView);
+ previewViewAtomicReference.set(previewView);
+ });
+ return previewViewAtomicReference.get();
}
private void startPreview(@NonNull final PreviewView previewView) {
diff --git a/camera/camera-view/src/androidTest/java/androidx/camera/view/PreviewViewStreamStateTest.kt b/camera/camera-view/src/androidTest/java/androidx/camera/view/PreviewViewStreamStateTest.kt
index 656cf67..1bd466d 100644
--- a/camera/camera-view/src/androidTest/java/androidx/camera/view/PreviewViewStreamStateTest.kt
+++ b/camera/camera-view/src/androidTest/java/androidx/camera/view/PreviewViewStreamStateTest.kt
@@ -63,6 +63,7 @@
private var mIsSetup = false
private lateinit var mLifecycle: FakeLifecycleOwner
private lateinit var mCameraProvider: ProcessCameraProvider
+
@get:Rule
var mCameraPermissionRule = GrantPermissionRule.grant(android.Manifest.permission.CAMERA)
@@ -89,7 +90,9 @@
val config = Camera2Config.defaultConfig()
CameraX.initialize(context, config)
mLifecycle = FakeLifecycleOwner()
- mPreviewView = PreviewView(context)
+ mInstrumentation.runOnMainSync {
+ mPreviewView = PreviewView(context)
+ }
setContentView(mPreviewView)
mPreviewView.implementationMode = implMode
diff --git a/camera/camera-view/src/androidTest/java/androidx/camera/view/PreviewViewTest.java b/camera/camera-view/src/androidTest/java/androidx/camera/view/PreviewViewTest.java
index 06942d6..d08119f 100644
--- a/camera/camera-view/src/androidTest/java/androidx/camera/view/PreviewViewTest.java
+++ b/camera/camera-view/src/androidTest/java/androidx/camera/view/PreviewViewTest.java
@@ -34,11 +34,13 @@
import android.app.Instrumentation;
import android.content.Context;
import android.graphics.Bitmap;
+import android.graphics.PixelFormat;
import android.graphics.SurfaceTexture;
import android.graphics.drawable.ColorDrawable;
import android.os.Build;
import android.util.Size;
import android.view.LayoutInflater;
+import android.view.Surface;
import android.view.SurfaceView;
import android.view.TextureView;
import android.view.View;
@@ -53,7 +55,10 @@
import androidx.camera.core.MeteringPointFactory;
import androidx.camera.core.Preview;
import androidx.camera.core.SurfaceRequest;
+import androidx.camera.core.impl.utils.executor.CameraXExecutors;
+import androidx.camera.core.impl.utils.futures.FutureCallback;
import androidx.camera.core.impl.utils.futures.Futures;
+import androidx.camera.testing.SurfaceFormatUtil;
import androidx.camera.testing.fakes.FakeActivity;
import androidx.camera.testing.fakes.FakeCamera;
import androidx.camera.testing.fakes.FakeCameraInfoInternal;
@@ -99,16 +104,24 @@
FakeActivity.class);
private final Context mContext = ApplicationProvider.getApplicationContext();
private SurfaceRequest mSurfaceRequest;
+ private PreviewView mPreviewView;
+ private MeteringPointFactory mMeteringPointFactory;
- private SurfaceRequest createSurfaceRequest(CameraInfo cameraInfo) {
- return createSurfaceRequest(DEFAULT_SURFACE_SIZE, cameraInfo);
+ private SurfaceRequest createSurfaceRequest(CameraInfo cameraInfo,
+ boolean isRGBA8888Required) {
+ return createSurfaceRequest(DEFAULT_SURFACE_SIZE, cameraInfo, isRGBA8888Required);
}
- private SurfaceRequest createSurfaceRequest(Size size, CameraInfo cameraInfo) {
+ private SurfaceRequest createSurfaceRequest(CameraInfo cameraInfo) {
+ return createSurfaceRequest(DEFAULT_SURFACE_SIZE, cameraInfo, false);
+ }
+
+ private SurfaceRequest createSurfaceRequest(Size size, CameraInfo cameraInfo,
+ boolean isRGBA8888Required) {
FakeCamera fakeCamera = spy(new FakeCamera());
when(fakeCamera.getCameraInfo()).thenReturn(cameraInfo);
- return new SurfaceRequest(size, fakeCamera, null);
+ return new SurfaceRequest(size, fakeCamera, isRGBA8888Required);
}
private CountDownLatch mCountDownLatch = new CountDownLatch(1);
@@ -281,22 +294,60 @@
}
@Test
+ public void correctSurfacePixelFormat_whenRGBA8888IsRequired()
+ throws Throwable {
+
+ final CameraInfo cameraInfo = createCameraInfo(CameraInfo.IMPLEMENTATION_TYPE_CAMERA2);
+ mSurfaceRequest = createSurfaceRequest(cameraInfo, true);
+ ListenableFuture<Surface> future = mSurfaceRequest.getDeferrableSurface().getSurface();
+
+ mActivityRule.runOnUiThread(new Runnable() {
+ @Override
+ public void run() {
+
+ final PreviewView previewView = new PreviewView(mContext);
+ setContentView(previewView);
+
+ previewView.setImplementationMode(PERFORMANCE);
+ Preview.SurfaceProvider surfaceProvider = previewView.getSurfaceProvider();
+ surfaceProvider.onSurfaceRequested(mSurfaceRequest);
+ }
+ });
+ final Surface[] surface = new Surface[1];
+ CountDownLatch countDownLatch = new CountDownLatch(1);
+
+ Futures.addCallback(future, new FutureCallback<Surface>() {
+ @Override
+ public void onSuccess(@Nullable Surface result) {
+ surface[0] = result;
+ countDownLatch.countDown();
+ }
+
+ @Override
+ public void onFailure(Throwable t) {
+ }
+ }, CameraXExecutors.directExecutor());
+
+ assertThat(countDownLatch.await(1, TimeUnit.SECONDS)).isTrue();
+ assertThat(SurfaceFormatUtil.getSurfaceFormat(surface[0])).isEqualTo(PixelFormat.RGBA_8888);
+ }
+
+ @Test
public void canCreateValidMeteringPoint() throws Exception {
final CameraInfo cameraInfo = createCameraInfo(90,
CameraInfo.IMPLEMENTATION_TYPE_CAMERA2, CameraSelector.LENS_FACING_BACK);
- final PreviewView previewView = new PreviewView(mContext);
-
mInstrumentation.runOnMainSync(() -> {
- setContentView(previewView);
+ mPreviewView = new PreviewView(mContext);
+ setContentView(mPreviewView);
mSurfaceRequest = createSurfaceRequest(cameraInfo);
- Preview.SurfaceProvider surfaceProvider = previewView.getSurfaceProvider();
+ Preview.SurfaceProvider surfaceProvider = mPreviewView.getSurfaceProvider();
surfaceProvider.onSurfaceRequested(mSurfaceRequest);
});
- waitForLayoutReady(previewView);
+ waitForLayoutReady(mPreviewView);
- MeteringPointFactory factory = previewView.getMeteringPointFactory();
+ MeteringPointFactory factory = mPreviewView.getMeteringPointFactory();
MeteringPoint point = factory.createPoint(100, 100);
assertPointIsValid(point);
}
@@ -311,22 +362,22 @@
final CameraInfo cameraInfo = createCameraInfo(90,
CameraInfo.IMPLEMENTATION_TYPE_CAMERA2, CameraSelector.LENS_FACING_BACK);
- final PreviewView previewView = new PreviewView(mContext);
- MeteringPointFactory factory = previewView.getMeteringPointFactory();
-
mInstrumentation.runOnMainSync(() -> {
- setContentView(previewView);
+ mPreviewView = new PreviewView(mContext);
+ mMeteringPointFactory = mPreviewView.getMeteringPointFactory();
+
+ setContentView(mPreviewView);
mSurfaceRequest = createSurfaceRequest(cameraInfo);
- Preview.SurfaceProvider surfaceProvider = previewView.getSurfaceProvider();
+ Preview.SurfaceProvider surfaceProvider = mPreviewView.getSurfaceProvider();
surfaceProvider.onSurfaceRequested(mSurfaceRequest);
});
- changeViewSize(previewView, 1000, 1000);
- MeteringPoint point1 = factory.createPoint(100, 100);
+ changeViewSize(mPreviewView, 1000, 1000);
+ MeteringPoint point1 = mMeteringPointFactory.createPoint(100, 100);
- changeViewSize(previewView, 500, 400);
+ changeViewSize(mPreviewView, 500, 400);
- MeteringPoint point2 = factory.createPoint(100, 100);
+ MeteringPoint point2 = mMeteringPointFactory.createPoint(100, 100);
assertPointIsValid(point1);
assertPointIsValid(point2);
@@ -361,24 +412,22 @@
public void meteringPointFactoryAutoAdjusted_whenScaleTypeChanged() throws Exception {
final CameraInfo cameraInfo = createCameraInfo(90,
CameraInfo.IMPLEMENTATION_TYPE_CAMERA2, CameraSelector.LENS_FACING_BACK);
-
- final PreviewView previewView = new PreviewView(mContext);
- MeteringPointFactory factory = previewView.getMeteringPointFactory();
-
mInstrumentation.runOnMainSync(() -> {
- setContentView(previewView);
+ mPreviewView = new PreviewView(mContext);
+ mMeteringPointFactory = mPreviewView.getMeteringPointFactory();
+ setContentView(mPreviewView);
mSurfaceRequest = createSurfaceRequest(cameraInfo);
- Preview.SurfaceProvider surfaceProvider = previewView.getSurfaceProvider();
+ Preview.SurfaceProvider surfaceProvider = mPreviewView.getSurfaceProvider();
surfaceProvider.onSurfaceRequested(mSurfaceRequest);
});
// Surface resolution is 640x480 , set a different size for PreviewView.
- changeViewSize(previewView, 800, 700);
+ changeViewSize(mPreviewView, 800, 700);
- previewView.setScaleType(PreviewView.ScaleType.FILL_CENTER);
- MeteringPoint point1 = factory.createPoint(100, 100);
+ mPreviewView.setScaleType(PreviewView.ScaleType.FILL_CENTER);
+ MeteringPoint point1 = mMeteringPointFactory.createPoint(100, 100);
- previewView.setScaleType(PreviewView.ScaleType.FIT_START);
- MeteringPoint point2 = factory.createPoint(100, 100);
+ mPreviewView.setScaleType(PreviewView.ScaleType.FIT_START);
+ MeteringPoint point2 = mMeteringPointFactory.createPoint(100, 100);
assertPointIsValid(point1);
assertPointIsValid(point2);
@@ -393,29 +442,28 @@
final CameraInfo cameraInfo2 = createCameraInfo(270,
CameraInfo.IMPLEMENTATION_TYPE_CAMERA2, CameraSelector.LENS_FACING_FRONT);
- final PreviewView previewView = new PreviewView(mContext);
- MeteringPointFactory factory = previewView.getMeteringPointFactory();
-
mInstrumentation.runOnMainSync(() -> {
- setContentView(previewView);
+ mPreviewView = new PreviewView(mContext);
+ mMeteringPointFactory = mPreviewView.getMeteringPointFactory();
+ setContentView(mPreviewView);
mSurfaceRequest = createSurfaceRequest(cameraInfo1);
- Preview.SurfaceProvider surfaceProvider = previewView.getSurfaceProvider();
+ Preview.SurfaceProvider surfaceProvider = mPreviewView.getSurfaceProvider();
surfaceProvider.onSurfaceRequested(mSurfaceRequest);
});
- changeViewSize(previewView, 1000, 1000);
+ changeViewSize(mPreviewView, 1000, 1000);
// get a MeteringPoint from a non-center point.
- MeteringPoint point1 = factory.createPoint(100, 120);
+ MeteringPoint point1 = mMeteringPointFactory.createPoint(100, 120);
mInstrumentation.runOnMainSync(() -> {
- setContentView(previewView);
+ setContentView(mPreviewView);
mSurfaceRequest = createSurfaceRequest(cameraInfo2);
- Preview.SurfaceProvider surfaceProvider = previewView.getSurfaceProvider();
+ Preview.SurfaceProvider surfaceProvider = mPreviewView.getSurfaceProvider();
surfaceProvider.onSurfaceRequested(mSurfaceRequest);
});
- MeteringPoint point2 = factory.createPoint(100, 120);
+ MeteringPoint point2 = mMeteringPointFactory.createPoint(100, 120);
assertPointIsValid(point1);
assertPointIsValid(point2);
@@ -618,9 +666,8 @@
container.get().setLayoutParams(layoutParams);
// Creates surface provider and request surface for 1080p surface size.
- Preview.SurfaceProvider surfaceProvider =
- previewView.get().getSurfaceProvider();
- mSurfaceRequest = createSurfaceRequest(bufferSize, cameraInfo);
+ Preview.SurfaceProvider surfaceProvider = previewView.get().getSurfaceProvider();
+ mSurfaceRequest = createSurfaceRequest(bufferSize, cameraInfo, false);
surfaceProvider.onSurfaceRequested(mSurfaceRequest);
// Retrieves the TextureView
diff --git a/camera/camera-view/src/androidTest/java/androidx/camera/view/SurfaceViewImplementationTest.kt b/camera/camera-view/src/androidTest/java/androidx/camera/view/SurfaceViewImplementationTest.kt
index d604189..a67804d 100644
--- a/camera/camera-view/src/androidTest/java/androidx/camera/view/SurfaceViewImplementationTest.kt
+++ b/camera/camera-view/src/androidTest/java/androidx/camera/view/SurfaceViewImplementationTest.kt
@@ -70,7 +70,7 @@
mParent = FrameLayout(context)
setContentView(mParent)
- mSurfaceRequest = SurfaceRequest(ANY_SIZE, FakeCamera(), null)
+ mSurfaceRequest = SurfaceRequest(ANY_SIZE, FakeCamera(), false)
mImplementation = SurfaceViewImplementation()
mImplementation.init(mParent, PreviewTransform())
}
diff --git a/camera/camera-view/src/androidTest/java/androidx/camera/view/TextureViewImplementationTest.java b/camera/camera-view/src/androidTest/java/androidx/camera/view/TextureViewImplementationTest.java
index 2a7182b..3644ede 100644
--- a/camera/camera-view/src/androidTest/java/androidx/camera/view/TextureViewImplementationTest.java
+++ b/camera/camera-view/src/androidTest/java/androidx/camera/view/TextureViewImplementationTest.java
@@ -340,7 +340,7 @@
@NonNull
private SurfaceRequest getSurfaceRequest() {
if (mSurfaceRequest == null) {
- mSurfaceRequest = new SurfaceRequest(ANY_SIZE, new FakeCamera(), null);
+ mSurfaceRequest = new SurfaceRequest(ANY_SIZE, new FakeCamera(), false);
}
return mSurfaceRequest;
diff --git a/camera/camera-view/src/main/java/androidx/camera/view/CameraController.java b/camera/camera-view/src/main/java/androidx/camera/view/CameraController.java
index 719230f..3064892 100644
--- a/camera/camera-view/src/main/java/androidx/camera/view/CameraController.java
+++ b/camera/camera-view/src/main/java/androidx/camera/view/CameraController.java
@@ -30,8 +30,11 @@
import androidx.camera.core.CameraInfo;
import androidx.camera.core.CameraSelector;
import androidx.camera.core.ExperimentalUseCaseGroup;
+import androidx.camera.core.FocusMeteringAction;
import androidx.camera.core.ImageCapture;
import androidx.camera.core.ImageProxy;
+import androidx.camera.core.Logger;
+import androidx.camera.core.MeteringPoint;
import androidx.camera.core.Preview;
import androidx.camera.core.TorchState;
import androidx.camera.core.UseCase;
@@ -112,7 +115,6 @@
@Nullable
ProcessCameraProvider mCameraProvider;
- // TODO(b/148791439): Update PreviewView to support pinch-to-zoom and tap-to-focus.
private boolean mPinchToZoomEnabled = true;
private boolean mTapToFocusEnabled = true;
@@ -521,6 +523,50 @@
}
/**
+ * Called by {@link PreviewView} for a pinch-to-zoom event.
+ */
+ @SuppressWarnings("FutureReturnValueIgnored")
+ void onPinchToZoom(float pinchToZoomScale) {
+ if (mCamera == null || !mPinchToZoomEnabled) {
+ Logger.d(TAG, "Pinch to zoom disabled.");
+ return;
+ }
+ Logger.d(TAG, "Pinch to zoom with scale: " + pinchToZoomScale);
+
+ ZoomState zoomState = getZoomState().getValue();
+ if (zoomState == null) {
+ return;
+ }
+ float clampedRatio = zoomState.getZoomRatio() * speedUpZoomBy2X(pinchToZoomScale);
+ // Clamp the ratio with the zoom range.
+ clampedRatio = Math.min(Math.max(clampedRatio, zoomState.getMinZoomRatio()),
+ zoomState.getMaxZoomRatio());
+ setZoomRatio(clampedRatio);
+ }
+
+ private float speedUpZoomBy2X(float scaleFactor) {
+ if (scaleFactor > 1f) {
+ return 1.0f + (scaleFactor - 1.0f) * 2;
+ } else {
+ return 1.0f - (1.0f - scaleFactor) * 2;
+ }
+ }
+
+ /**
+ * Called by {@link PreviewView} for a tap-to-focus event.
+ */
+ @SuppressWarnings("FutureReturnValueIgnored")
+ void onTapToFocus(MeteringPoint meteringPoint) {
+ if (mCamera == null || !mTapToFocusEnabled) {
+ Logger.d(TAG, "Tap to focus disabled. ");
+ return;
+ }
+ Logger.d(TAG, "Tap to focus: " + meteringPoint.getX() + "x" + meteringPoint.getY());
+ mCamera.getCameraControl().startFocusAndMetering(
+ new FocusMeteringAction.Builder(meteringPoint).build());
+ }
+
+ /**
* Returns whether tap-to-focus is enabled.
*
* <p> By default tap-to-focus is enabled.
@@ -546,7 +592,6 @@
mTapToFocusEnabled = enabled;
}
-
/**
* Returns a {@link LiveData} of {@link ZoomState}.
*
diff --git a/camera/camera-view/src/main/java/androidx/camera/view/PreviewView.java b/camera/camera-view/src/main/java/androidx/camera/view/PreviewView.java
index 5c2be74..0ec749a 100644
--- a/camera/camera-view/src/main/java/androidx/camera/view/PreviewView.java
+++ b/camera/camera-view/src/main/java/androidx/camera/view/PreviewView.java
@@ -25,10 +25,13 @@
import android.os.Build;
import android.util.AttributeSet;
import android.view.Display;
+import android.view.MotionEvent;
+import android.view.ScaleGestureDetector;
import android.view.Surface;
import android.view.SurfaceView;
import android.view.TextureView;
import android.view.View;
+import android.view.ViewConfiguration;
import android.view.WindowManager;
import android.widget.FrameLayout;
@@ -39,7 +42,9 @@
import androidx.annotation.RestrictTo;
import androidx.annotation.UiThread;
import androidx.annotation.VisibleForTesting;
+import androidx.annotation.experimental.UseExperimental;
import androidx.camera.core.CameraInfo;
+import androidx.camera.core.ExperimentalUseCaseGroup;
import androidx.camera.core.FocusMeteringAction;
import androidx.camera.core.Logger;
import androidx.camera.core.MeteringPoint;
@@ -51,6 +56,7 @@
import androidx.camera.core.impl.utils.Threads;
import androidx.camera.view.preview.transform.PreviewTransform;
import androidx.core.content.ContextCompat;
+import androidx.core.util.Preconditions;
import androidx.lifecycle.LiveData;
import androidx.lifecycle.MutableLiveData;
@@ -96,11 +102,11 @@
PreviewTransform mPreviewTransform = new PreviewTransform();
@NonNull
- private MutableLiveData<StreamState> mPreviewStreamStateLiveData =
+ private final MutableLiveData<StreamState> mPreviewStreamStateLiveData =
new MutableLiveData<>(StreamState.IDLE);
@Nullable
- private AtomicReference<PreviewStreamStateObserver> mActiveStreamStateObserver =
+ private final AtomicReference<PreviewStreamStateObserver> mActiveStreamStateObserver =
new AtomicReference<>();
// Synthetic access
@SuppressWarnings("WeakerAccess")
@@ -110,6 +116,14 @@
PreviewViewMeteringPointFactory mPreviewViewMeteringPointFactory =
new PreviewViewMeteringPointFactory();
+ // Detector for zoom-to-scale.
+ @NonNull
+ private final ScaleGestureDetector mScaleGestureDetector;
+
+ // Coordinates of the touchdown event for tap-to-focus.
+ private float mDownX = 0F;
+ private float mDownY = 0F;
+
private final OnLayoutChangeListener mOnLayoutChangeListener = new OnLayoutChangeListener() {
@Override
public void onLayoutChange(View v, int left, int top, int right, int bottom, int oldLeft,
@@ -134,8 +148,9 @@
CameraInternal camera = (CameraInternal) surfaceRequest.getCamera();
mPreviewTransform.setSensorDimensionFlipNeeded(
isSensorDimensionFlipNeeded(camera.getCameraInfo()));
- mImplementation = shouldUseTextureView(camera.getCameraInfo(), mImplementationMode)
- ? new TextureViewImplementation() : new SurfaceViewImplementation();
+ mImplementation = surfaceRequest.isRGBA8888Required() || shouldUseTextureView(
+ camera.getCameraInfo(), mImplementationMode) ? new TextureViewImplementation()
+ : new SurfaceViewImplementation();
mImplementation.init(this, mPreviewTransform);
PreviewStreamStateObserver streamStateObserver =
@@ -194,6 +209,9 @@
attributes.recycle();
}
+ mScaleGestureDetector = new ScaleGestureDetector(
+ context, new PinchToZoomOnScaleGestureListener());
+
// Set background only if it wasn't already set. A default background prevents the content
// behind the PreviewView from being visible before the preview starts streaming.
if (getBackground() == null) {
@@ -228,6 +246,52 @@
}
}
+ @Override
+ public boolean onTouchEvent(@NonNull MotionEvent event) {
+ if (mCameraController == null) {
+ // Do not handle touch event if no controller.
+ return false;
+ }
+ // Detect pinch-to-zoom
+ mScaleGestureDetector.onTouchEvent(event);
+
+ // Detect tap-to-focus.
+ if (event.getPointerCount() >= 2) {
+ // Do not handle touch event if it's not a single finger touch.
+ return false;
+ }
+ switch (event.getAction()) {
+ case MotionEvent.ACTION_DOWN:
+ mDownX = event.getX();
+ mDownY = event.getY();
+ return true;
+ case MotionEvent.ACTION_UP:
+ if (isTapEvent(event)) {
+ mCameraController.onTapToFocus(
+ mPreviewViewMeteringPointFactory.createPoint(event.getX(),
+ event.getY()));
+ }
+ return true;
+ default:
+ return false;
+ }
+ }
+
+ /**
+ * Checks if the up event is a tap on the view.
+ */
+ private boolean isTapEvent(MotionEvent event) {
+ Preconditions.checkArgument(event.getAction() == MotionEvent.ACTION_UP);
+ // Elapsed time since last touch down.
+ long elapsedTime = event.getEventTime() - event.getDownTime();
+ // Distance since last touch down.
+ float distance = (float) Math.hypot(event.getX() - mDownX, event.getY() - mDownY);
+ // This should be ViewConfiguration#getTapTimeout(), but system time out is 100ms which
+ // is too short.
+ return elapsedTime < ViewConfiguration.getLongPressTimeout()
+ && distance < ViewConfiguration.get(getContext()).getScaledTouchSlop();
+ }
+
/**
* Sets the {@link ImplementationMode} for the {@link PreviewView}.
*
@@ -267,6 +331,7 @@
*/
@NonNull
@UiThread
+ @UseExperimental(markerClass = ExperimentalUseCaseGroup.class)
public Preview.SurfaceProvider getSurfaceProvider() {
Threads.checkMainThread();
return mSurfaceProvider;
@@ -613,6 +678,20 @@
}
/**
+ * GestureListener that speeds up scale factor and sends it to controller.
+ */
+ class PinchToZoomOnScaleGestureListener extends
+ ScaleGestureDetector.SimpleOnScaleGestureListener {
+ @Override
+ public boolean onScale(ScaleGestureDetector detector) {
+ if (mCameraController != null) {
+ mCameraController.onPinchToZoom(detector.getScaleFactor());
+ }
+ return true;
+ }
+ }
+
+ /**
* Sets the {@link CameraController}.
*
* <p> The controller creates and manages the {@link Preview} that backs the
diff --git a/camera/camera-view/src/test/java/androidx/camera/view/PreviewViewTest.java b/camera/camera-view/src/test/java/androidx/camera/view/PreviewViewTest.java
index d89c1ab..0388d57 100644
--- a/camera/camera-view/src/test/java/androidx/camera/view/PreviewViewTest.java
+++ b/camera/camera-view/src/test/java/androidx/camera/view/PreviewViewTest.java
@@ -64,7 +64,7 @@
private SurfaceRequest createSurfaceRequest(CameraInfo cameraInfo) {
FakeCamera fakeCamera = spy(new FakeCamera());
when(fakeCamera.getCameraInfo()).thenReturn(cameraInfo);
- return new SurfaceRequest(new Size(640, 480), fakeCamera, null);
+ return new SurfaceRequest(new Size(640, 480), fakeCamera, false);
}
@After
diff --git a/camera/integration-tests/camerapipetestapp/src/main/java/androidx/camera/integration/camera2/pipe/CameraPipeActivity.kt b/camera/integration-tests/camerapipetestapp/src/main/java/androidx/camera/integration/camera2/pipe/CameraPipeActivity.kt
index 2b19b40..e87a73d 100644
--- a/camera/integration-tests/camerapipetestapp/src/main/java/androidx/camera/integration/camera2/pipe/CameraPipeActivity.kt
+++ b/camera/integration-tests/camerapipetestapp/src/main/java/androidx/camera/integration/camera2/pipe/CameraPipeActivity.kt
@@ -108,14 +108,24 @@
frameNumber: FrameNumber,
timestamp: CameraTimestamp
) {
+// Log.i("PJR", "OnStart: $frameNumber")
// TODO: Implement the onStarted event to get frameNumber(s) and timestamps
}
+ override fun onTotalCaptureResult(
+ requestMetadata: RequestMetadata,
+ frameNumber: FrameNumber,
+ totalCaptureResult: FrameInfo
+ ) {
+// Log.i("PJR", "onTotalCaptureResult: $frameNumber")
+ }
+
override fun onComplete(
requestMetadata: RequestMetadata,
frameNumber: FrameNumber,
result: FrameInfo
) {
+ Log.i("PJR", "onComplete: $frameNumber")
// TODO: Implement the onComplete event to write out Metadata
}
}))
diff --git a/camera/integration-tests/camerapipetestapp/src/main/java/androidx/camera/integration/camera2/pipe/SimpleCamera.kt b/camera/integration-tests/camerapipetestapp/src/main/java/androidx/camera/integration/camera2/pipe/SimpleCamera.kt
index f15b86a..acf14ee 100644
--- a/camera/integration-tests/camerapipetestapp/src/main/java/androidx/camera/integration/camera2/pipe/SimpleCamera.kt
+++ b/camera/integration-tests/camerapipetestapp/src/main/java/androidx/camera/integration/camera2/pipe/SimpleCamera.kt
@@ -106,7 +106,6 @@
cameraGraph.setSurface(viewfinderStream.id, surface)
}
})
-
val yuvStream = cameraGraph.streams[yuvStreamConfig]!!
cameraGraph.acquireSessionOrNull()!!.use {
it.setRepeating(
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 1d51e69..03294e7 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
@@ -36,13 +36,17 @@
import android.os.SystemClock;
import android.provider.MediaStore;
import android.util.Log;
+import android.util.Range;
import android.util.Rational;
import android.view.Display;
+import android.view.MotionEvent;
+import android.view.ScaleGestureDetector;
import android.view.View;
import android.view.ViewStub;
import android.widget.Button;
import android.widget.CompoundButton;
import android.widget.ImageButton;
+import android.widget.SeekBar;
import android.widget.TextView;
import android.widget.Toast;
import android.widget.ToggleButton;
@@ -60,6 +64,7 @@
import androidx.camera.core.CameraInfo;
import androidx.camera.core.CameraSelector;
import androidx.camera.core.ExperimentalUseCaseGroup;
+import androidx.camera.core.ExposureState;
import androidx.camera.core.ImageAnalysis;
import androidx.camera.core.ImageCapture;
import androidx.camera.core.ImageCaptureException;
@@ -74,6 +79,7 @@
import androidx.camera.lifecycle.ExperimentalUseCaseGroupLifecycle;
import androidx.camera.lifecycle.ProcessCameraProvider;
import androidx.core.content.ContextCompat;
+import androidx.core.math.MathUtils;
import androidx.lifecycle.MutableLiveData;
import androidx.lifecycle.ViewModelProvider;
import androidx.test.espresso.IdlingResource;
@@ -150,6 +156,10 @@
private TextView mTextView;
private ImageButton mTorchButton;
private ToggleButton mCaptureQualityToggle;
+ private Button mPlusEV;
+ private Button mDecEV;
+ private TextView mZoomRatioLabel;
+ private SeekBar mZoomSeekBar;
private OpenGLRenderer mPreviewRenderer;
private DisplayManager.DisplayListener mDisplayListener;
@@ -176,6 +186,28 @@
}
};
+ private FutureCallback<Integer> mEVFutureCallback = new FutureCallback<Integer>() {
+
+ @Override
+ @UseExperimental(markerClass = androidx.camera.core.ExperimentalExposureCompensation.class)
+ public void onSuccess(@Nullable Integer result) {
+ CameraInfo cameraInfo = getCameraInfo();
+ if (cameraInfo != null) {
+ ExposureState exposureState = cameraInfo.getExposureState();
+ float ev = result * exposureState.getExposureCompensationStep().floatValue();
+ Log.d(TAG, "success new EV: " + ev);
+ Toast.makeText(getApplicationContext(), String.format("EV: %.2f", ev),
+ Toast.LENGTH_SHORT).show();
+ }
+ }
+
+ @Override
+ public void onFailure(@NonNull Throwable t) {
+ Log.d(TAG, "failed " + t);
+ Toast.makeText(getApplicationContext(), "Fail to set EV", Toast.LENGTH_SHORT).show();
+ }
+ };
+
// Listener that handles all ToggleButton events.
private CompoundButton.OnCheckedChangeListener mOnCheckedChangeListener =
(compoundButton, isChecked) -> tryBindUseCases();
@@ -272,6 +304,13 @@
return mPhotoToggle.isChecked() && cameraInfo != null && cameraInfo.hasFlashUnit();
}
+ @UseExperimental(markerClass = androidx.camera.core.ExperimentalExposureCompensation.class)
+ private boolean isExposureCompensationSupported() {
+ CameraInfo cameraInfo = getCameraInfo();
+ return cameraInfo != null
+ && cameraInfo.getExposureState().isExposureCompensationSupported();
+ }
+
private void setUpFlashButton() {
mFlashButton.setOnClickListener(v -> {
@ImageCapture.FlashMode int flashMode = getImageCapture().getFlashMode();
@@ -404,6 +443,51 @@
});
}
+ @UseExperimental(markerClass = androidx.camera.core.ExperimentalExposureCompensation.class)
+ private void setUpEVButton() {
+ mPlusEV.setOnClickListener(v -> {
+ Objects.requireNonNull(getCameraInfo());
+ Objects.requireNonNull(getCameraControl());
+
+ ExposureState exposureState = getCameraInfo().getExposureState();
+ Range<Integer> range = exposureState.getExposureCompensationRange();
+ int ec = exposureState.getExposureCompensationIndex();
+
+ if (range.contains(ec + 1)) {
+ ListenableFuture<Integer> future =
+ getCameraControl().setExposureCompensationIndex(ec + 1);
+ Futures.addCallback(future, mEVFutureCallback,
+ CameraXExecutors.mainThreadExecutor());
+ } else {
+ Toast.makeText(getApplicationContext(), String.format("EV: %.2f",
+ range.getUpper()
+ * exposureState.getExposureCompensationStep().floatValue()),
+ Toast.LENGTH_LONG).show();
+ }
+ });
+
+ mDecEV.setOnClickListener(v -> {
+ Objects.requireNonNull(getCameraInfo());
+ Objects.requireNonNull(getCameraControl());
+
+ ExposureState exposureState = getCameraInfo().getExposureState();
+ Range<Integer> range = exposureState.getExposureCompensationRange();
+ int ec = exposureState.getExposureCompensationIndex();
+
+ if (range.contains(ec - 1)) {
+ ListenableFuture<Integer> future =
+ getCameraControl().setExposureCompensationIndex(ec - 1);
+ Futures.addCallback(future, mEVFutureCallback,
+ CameraXExecutors.mainThreadExecutor());
+ } else {
+ Toast.makeText(getApplicationContext(), String.format("EV: %.2f",
+ range.getLower()
+ * exposureState.getExposureCompensationStep().floatValue()),
+ Toast.LENGTH_LONG).show();
+ }
+ });
+ }
+
private void updateButtonsUi() {
mRecord.setEnabled(mVideoToggle.isChecked());
mTakePicture.setEnabled(mPhotoToggle.isChecked());
@@ -425,6 +509,8 @@
break;
}
}
+ mPlusEV.setEnabled(isExposureCompensationSupported());
+ mDecEV.setEnabled(isExposureCompensationSupported());
}
private void setUpButtonEvents() {
@@ -438,6 +524,7 @@
setUpTakePictureButton();
setUpCameraDirectionButton();
setUpTorchButton();
+ setUpEVButton();
mCaptureQualityToggle.setOnCheckedChangeListener(mOnCheckedChangeListener);
}
@@ -465,10 +552,15 @@
mCameraDirectionButton = findViewById(R.id.direction_toggle);
mTorchButton = findViewById(R.id.torch_toggle);
mCaptureQualityToggle = findViewById(R.id.capture_quality);
+ mPlusEV = findViewById(R.id.plus_ev_toggle);
+ mDecEV = findViewById(R.id.dec_ev_toggle);
+ mZoomSeekBar = findViewById(R.id.seekBar);
+ mZoomRatioLabel = findViewById(R.id.zoomRatio);
mTextView = findViewById(R.id.textView);
setUpButtonEvents();
+ setupPinchToZoom();
mImageAnalysisResult.observe(
this,
@@ -745,9 +837,124 @@
}
mCamera = mCameraProvider.bindToLifecycle(this, mCurrentCameraSelector,
useCaseGroupBuilder.build());
+ setupZoomSeeker();
return mCamera;
}
+ private static final int MAX_SEEKBAR_VALUE = 100000;
+
+ void showZoomRatioIsAlive() {
+ mZoomRatioLabel.setTextColor(getResources().getColor(R.color.zoom_ratio_activated));
+ }
+
+ void showNormalZoomRatio() {
+ mZoomRatioLabel.setTextColor(getResources().getColor(R.color.zoom_ratio_set));
+ }
+
+ ScaleGestureDetector.SimpleOnScaleGestureListener mScaleGestureListener =
+ new ScaleGestureDetector.SimpleOnScaleGestureListener() {
+ @Override
+ public boolean onScale(ScaleGestureDetector detector) {
+ if (mCamera == null) {
+ return true;
+ }
+
+ CameraInfo cameraInfo = mCamera.getCameraInfo();
+ CameraControl cameraControl = mCamera.getCameraControl();
+ float newZoom =
+ cameraInfo.getZoomState().getValue().getZoomRatio()
+ * detector.getScaleFactor();
+ float clampedNewZoom = MathUtils.clamp(newZoom,
+ cameraInfo.getZoomState().getValue().getMinZoomRatio(),
+ cameraInfo.getZoomState().getValue().getMaxZoomRatio());
+
+ Log.d(TAG, "setZoomRatio ratio: " + clampedNewZoom);
+ showNormalZoomRatio();
+ ListenableFuture<Void> listenableFuture = cameraControl.setZoomRatio(
+ clampedNewZoom);
+ Futures.addCallback(listenableFuture, new FutureCallback<Void>() {
+ @Override
+ public void onSuccess(@Nullable Void result) {
+ Log.d(TAG, "setZoomRatio onSuccess: " + clampedNewZoom);
+ showZoomRatioIsAlive();
+ }
+
+ @Override
+ public void onFailure(Throwable t) {
+ Log.d(TAG, "setZoomRatio failed, " + t);
+ }
+ }, ContextCompat.getMainExecutor(CameraXActivity.this));
+ return true;
+ }
+ };
+
+ private void setupPinchToZoom() {
+ ScaleGestureDetector scaleDetector = new ScaleGestureDetector(this, mScaleGestureListener);
+ mViewFinder.setOnTouchListener(new View.OnTouchListener() {
+ @Override
+ public boolean onTouch(View view, MotionEvent motionEvent) {
+ scaleDetector.onTouchEvent(motionEvent);
+
+ return true;
+ }
+ });
+
+
+ }
+
+ private void setupZoomSeeker() {
+ CameraControl cameraControl = mCamera.getCameraControl();
+ CameraInfo cameraInfo = mCamera.getCameraInfo();
+
+ mZoomSeekBar.setMax(MAX_SEEKBAR_VALUE);
+ mZoomSeekBar.setProgress(
+ (int) (cameraInfo.getZoomState().getValue().getLinearZoom() * MAX_SEEKBAR_VALUE));
+ mZoomSeekBar.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {
+ @Override
+ public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
+ if (!fromUser) {
+ return;
+ }
+
+ float percentage = (float) progress / MAX_SEEKBAR_VALUE;
+ showNormalZoomRatio();
+ ListenableFuture<Void> listenableFuture =
+ cameraControl.setLinearZoom(percentage);
+
+ Futures.addCallback(listenableFuture, new FutureCallback<Void>() {
+ @Override
+ public void onSuccess(@Nullable Void result) {
+ Log.d(TAG, "setZoomPercentage " + percentage + " onSuccess");
+ showZoomRatioIsAlive();
+ }
+
+ @Override
+ public void onFailure(Throwable t) {
+ Log.d(TAG, "setZoomPercentage " + percentage + " failed, " + t);
+ }
+ }, ContextCompat.getMainExecutor(CameraXActivity.this));
+ }
+
+ @Override
+ public void onStartTrackingTouch(SeekBar seekBar) {
+
+ }
+
+ @Override
+ public void onStopTrackingTouch(SeekBar seekBar) {
+
+ }
+ });
+
+ cameraInfo.getZoomState().removeObservers(this);
+ cameraInfo.getZoomState().observe(this,
+ state -> {
+ String str = String.format("%.2fx", state.getZoomRatio());
+ mZoomRatioLabel.setText(str);
+ mZoomSeekBar.setProgress((int) (MAX_SEEKBAR_VALUE * state.getLinearZoom()));
+ });
+ }
+
/** Gets the absolute path from a Uri. */
@Nullable
@SuppressWarnings("deprecation")
diff --git a/camera/integration-tests/coretestapp/src/main/java/androidx/camera/integration/core/OpenGLRenderer.java b/camera/integration-tests/coretestapp/src/main/java/androidx/camera/integration/core/OpenGLRenderer.java
index f3b1911..6f0d7ee 100644
--- a/camera/integration-tests/coretestapp/src/main/java/androidx/camera/integration/core/OpenGLRenderer.java
+++ b/camera/integration-tests/coretestapp/src/main/java/androidx/camera/integration/core/OpenGLRenderer.java
@@ -47,6 +47,7 @@
final class OpenGLRenderer {
private static final String TAG = "OpenGLRenderer";
private static final boolean DEBUG = false;
+
static {
System.loadLibrary("opengl_renderer_jni");
}
@@ -110,19 +111,24 @@
surfaceRequest.willNotProvideSurface();
return;
}
-
SurfaceTexture surfaceTexture = resetPreviewTexture(
surfaceRequest.getResolution());
Surface inputSurface = new Surface(surfaceTexture);
mNumOutstandingSurfaces++;
- Rect requestCropRect = surfaceRequest.getCropRect();
- if (!isCropRectFullTexture(requestCropRect)) {
- // Crop rect is pre-calculated. Use it directly.
- mPreviewCropRect = new RectF(requestCropRect);
- } else {
- // Crop rect needs to be calculated before drawing.
- mPreviewCropRect = null;
- }
+ surfaceRequest.setTransformationInfoListener(
+ mExecutor,
+ transformationInfo -> {
+ mMvpDirty = true;
+ // TODO(b/159127941): add the rotation to MVP transformation.
+ if (!isCropRectFullTexture(transformationInfo.getCropRect())) {
+ // Crop rect is pre-calculated. Use it directly.
+ mPreviewCropRect = new RectF(
+ transformationInfo.getCropRect());
+ } else {
+ // Crop rect needs to be calculated before drawing.
+ mPreviewCropRect = null;
+ }
+ });
surfaceRequest.provideSurface(
inputSurface,
mExecutor,
@@ -396,7 +402,7 @@
// +---------+ +----v----+
// (0,-1)
return 180;
- } else if (s == -1 && t == 0) {
+ } else if (s == -1 && t == 0) {
// (0,1)
// +----^----+ 270 deg +---------+
// | | | Rotation | |
@@ -637,8 +643,8 @@
+ "%.4f %.4f %.4f %.4f\n"
+ "%.4f %.4f %.4f %.4f\n"
+ "%.4f %.4f %.4f %.4f\n", label,
- matrix[offset], matrix[offset + 4], matrix[offset + 8], matrix[offset + 12],
- matrix[offset + 1], matrix[offset + 5], matrix[offset + 9], matrix[offset + 13],
+ matrix[offset], matrix[offset + 4], matrix[offset + 8], matrix[offset + 12],
+ matrix[offset + 1], matrix[offset + 5], matrix[offset + 9], matrix[offset + 13],
matrix[offset + 2], matrix[offset + 6], matrix[offset + 10], matrix[offset + 14],
matrix[offset + 3], matrix[offset + 7], matrix[offset + 11], matrix[offset + 15]));
}
diff --git a/camera/integration-tests/coretestapp/src/main/res/layout/activity_camera_xmain.xml b/camera/integration-tests/coretestapp/src/main/res/layout/activity_camera_xmain.xml
index 45331e0..61b778b 100644
--- a/camera/integration-tests/coretestapp/src/main/res/layout/activity_camera_xmain.xml
+++ b/camera/integration-tests/coretestapp/src/main/res/layout/activity_camera_xmain.xml
@@ -166,61 +166,113 @@
/>
<ImageButton
- android:id="@+id/torch_toggle"
+ android:id="@+id/direction_toggle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
- android:scaleType="fitXY"
- android:src="@drawable/ic_flashlight"
- android:translationZ="1dp"
+ android:layout_marginLeft="5dp"
+ android:layout_marginTop="1dp"
android:background="@android:drawable/btn_default"
- app:layout_constraintBottom_toBottomOf="parent"
- app:layout_constraintEnd_toEndOf="parent"
- app:layout_constraintHorizontal_bias="0.05"
- app:layout_constraintStart_toStartOf="@id/constraintLayout"
- app:layout_constraintTop_toTopOf="parent"
- app:layout_constraintVertical_bias="0.7" />
+ android:scaleType="fitXY"
+ android:src="@drawable/ic_camera_switch"
+ android:translationZ="1dp"
+ app:layout_constraintLeft_toLeftOf="parent"
+ app:layout_constraintTop_toBottomOf="@id/VideoToggle" />
<ImageButton
android:id="@+id/flash_toggle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
+ android:layout_marginLeft="5dp"
+ android:layout_marginTop="1dp"
+ android:background="@android:drawable/btn_default"
android:scaleType="fitXY"
android:src="@drawable/ic_flash_off"
android:translationZ="1dp"
- android:background="@android:drawable/btn_default"
- app:layout_constraintBottom_toBottomOf="parent"
- app:layout_constraintEnd_toEndOf="parent"
- app:layout_constraintHorizontal_bias="0.05"
- app:layout_constraintStart_toStartOf="@id/constraintLayout"
- app:layout_constraintTop_toTopOf="parent"
- app:layout_constraintVertical_bias="0.5" />
+ app:layout_constraintLeft_toRightOf="@id/direction_toggle"
+ app:layout_constraintTop_toBottomOf="@id/VideoToggle" />
<ImageButton
- android:id="@+id/direction_toggle"
+ android:id="@+id/torch_toggle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
- android:scaleType="fitXY"
- android:src="@drawable/ic_camera_switch"
- android:translationZ="1dp"
+ android:layout_marginLeft="5dp"
+ android:layout_marginTop="1dp"
android:background="@android:drawable/btn_default"
- app:layout_constraintBottom_toBottomOf="parent"
- app:layout_constraintEnd_toEndOf="parent"
- app:layout_constraintHorizontal_bias="0.05"
- app:layout_constraintStart_toStartOf="@id/constraintLayout"
- app:layout_constraintTop_toTopOf="parent"
- app:layout_constraintVertical_bias="0.3" />
+ android:scaleType="fitXY"
+ android:src="@drawable/ic_flashlight"
+ android:translationZ="1dp"
+ app:layout_constraintLeft_toRightOf="@id/flash_toggle"
+ app:layout_constraintTop_toBottomOf="@id/VideoToggle" />
<ToggleButton
android:id="@+id/capture_quality"
android:layout_width="46dp"
android:layout_height="wrap_content"
- android:translationZ="1dp"
+ android:layout_marginLeft="5dp"
+ android:layout_marginTop="1dp"
android:background="@android:drawable/btn_default"
- android:textSize="11dp"
- android:layout_marginTop="12dp"
android:textOn="@string/toggle_capture_quality_on"
android:textOff="@string/toggle_capture_quality_off"
- app:layout_constraintStart_toStartOf="@id/torch_toggle"
- app:layout_constraintTop_toBottomOf="@id/torch_toggle" />
+ android:textSize="11dp"
+ android:translationZ="1dp"
+ app:layout_constraintLeft_toRightOf="@id/torch_toggle"
+ app:layout_constraintTop_toBottomOf="@id/VideoToggle" />
+
+ <Button
+ android:id="@+id/plus_ev_toggle"
+ android:layout_width="46dp"
+ android:layout_height="wrap_content"
+ android:layout_marginLeft="5dp"
+ android:layout_marginTop="1dp"
+ android:background="@android:drawable/btn_default"
+ android:scaleType="fitXY"
+ android:text="@string/toggle_plus_ev"
+ android:textSize="10sp"
+ android:translationZ="1dp"
+ app:layout_constraintLeft_toRightOf="@id/capture_quality"
+ app:layout_constraintTop_toBottomOf="@id/VideoToggle" />
+
+ <Button
+ android:id="@+id/dec_ev_toggle"
+ android:layout_width="46dp"
+ android:layout_height="wrap_content"
+ android:layout_marginLeft="5dp"
+ android:layout_marginTop="1dp"
+ android:background="@android:drawable/btn_default"
+ android:scaleType="fitXY"
+ android:text="@string/toggle_dec_ev"
+ android:textSize="10sp"
+ android:translationZ="1dp"
+ app:layout_constraintLeft_toRightOf="@id/plus_ev_toggle"
+ app:layout_constraintTop_toBottomOf="@id/VideoToggle" />
+
+ <SeekBar
+ android:id="@+id/seekBar"
+ android:layout_width="250dp"
+ android:layout_height="60dp"
+ android:elevation="3dp"
+
+ app:layout_constraintBottom_toTopOf="@+id/textView"
+ app:layout_constraintEnd_toEndOf="parent"
+ app:layout_constraintHorizontal_bias="0.612"
+ app:layout_constraintStart_toStartOf="parent" />
+
+ <TextView
+ android:id="@+id/zoomRatio"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:elevation="3dp"
+ android:shadowColor="#000000"
+ android:shadowDx="1"
+ android:shadowDy="1"
+ android:shadowRadius= "1"
+ android:text="1x"
+ android:textSize="20dp"
+ android:textColor="#FFFFFF"
+ android:layout_marginRight="10dp"
+ app:layout_constraintBottom_toBottomOf="@+id/seekBar"
+ app:layout_constraintEnd_toEndOf="parent"
+ app:layout_constraintStart_toEndOf="@+id/seekBar"
+ app:layout_constraintTop_toTopOf="@+id/seekBar" />
</androidx.constraintlayout.widget.ConstraintLayout>
diff --git a/camera/integration-tests/coretestapp/src/main/res/values/colors.xml b/camera/integration-tests/coretestapp/src/main/res/values/colors.xml
index b9bae96..e2ee527 100644
--- a/camera/integration-tests/coretestapp/src/main/res/values/colors.xml
+++ b/camera/integration-tests/coretestapp/src/main/res/values/colors.xml
@@ -17,4 +17,8 @@
<resources>
<color name="usecase_toggle_on">#ffcccccc</color>
<color name="usecase_toggle_off">#ffff0000</color>
+
+ <color name="zoom_ratio_activated">#FFFF00</color>
+ <color name="zoom_ratio_set">#FFFFFF</color>
+
</resources>
\ No newline at end of file
diff --git a/camera/integration-tests/coretestapp/src/main/res/values/strings.xml b/camera/integration-tests/coretestapp/src/main/res/values/strings.xml
index de751463..9dbaf09 100644
--- a/camera/integration-tests/coretestapp/src/main/res/values/strings.xml
+++ b/camera/integration-tests/coretestapp/src/main/res/values/strings.xml
@@ -27,4 +27,6 @@
<string name="toggle_video_off">video\noff</string>
<string name="toggle_capture_quality_on">MAX</string>
<string name="toggle_capture_quality_off">MIN</string>
+ <string name="toggle_plus_ev">+EV</string>
+ <string name="toggle_dec_ev">-EV</string>
</resources>
\ No newline at end of file
diff --git a/camera/integration-tests/uiwidgetstestapp/src/androidTest/java/androidx/camera/integration/uiwidgets/rotations/ImageAnalysisBaseTest.kt b/camera/integration-tests/uiwidgetstestapp/src/androidTest/java/androidx/camera/integration/uiwidgets/rotations/ImageAnalysisBaseTest.kt
index 1966794..2fceaf8 100644
--- a/camera/integration-tests/uiwidgetstestapp/src/androidTest/java/androidx/camera/integration/uiwidgets/rotations/ImageAnalysisBaseTest.kt
+++ b/camera/integration-tests/uiwidgetstestapp/src/androidTest/java/androidx/camera/integration/uiwidgets/rotations/ImageAnalysisBaseTest.kt
@@ -40,19 +40,20 @@
*/
abstract class ImageAnalysisBaseTest<A : CameraActivity> {
+ protected val mDevice: UiDevice =
+ UiDevice.getInstance(InstrumentationRegistry.getInstrumentation())
+
protected fun setUp(lensFacing: Int) {
CoreAppTestUtil.assumeCompatibleDevice()
Assume.assumeTrue(CameraUtil.hasCameraWithLensFacing(lensFacing))
// Clear the device's UI and ensure it's in a natural orientation
CoreAppTestUtil.clearDeviceUI(InstrumentationRegistry.getInstrumentation())
- val device = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation())
- device.setOrientationNatural()
+ mDevice.setOrientationNatural()
}
protected fun tearDown() {
- val device = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation())
- device.unfreezeRotation()
+ mDevice.unfreezeRotation()
}
protected inline fun <reified A : CameraActivity> verifyRotation(
diff --git a/camera/integration-tests/uiwidgetstestapp/src/androidTest/java/androidx/camera/integration/uiwidgets/rotations/ImageAnalysisUnlockedOrientationTest.kt b/camera/integration-tests/uiwidgetstestapp/src/androidTest/java/androidx/camera/integration/uiwidgets/rotations/ImageAnalysisUnlockedOrientationTest.kt
index 7ebe9f6..2883dda 100644
--- a/camera/integration-tests/uiwidgetstestapp/src/androidTest/java/androidx/camera/integration/uiwidgets/rotations/ImageAnalysisUnlockedOrientationTest.kt
+++ b/camera/integration-tests/uiwidgetstestapp/src/androidTest/java/androidx/camera/integration/uiwidgets/rotations/ImageAnalysisUnlockedOrientationTest.kt
@@ -16,53 +16,37 @@
package androidx.camera.integration.uiwidgets.rotations
-import android.content.pm.ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE
-import android.content.pm.ActivityInfo.SCREEN_ORIENTATION_PORTRAIT
-import android.content.pm.ActivityInfo.SCREEN_ORIENTATION_REVERSE_LANDSCAPE
-import android.content.pm.ActivityInfo.SCREEN_ORIENTATION_REVERSE_PORTRAIT
-import android.content.res.Configuration
-import androidx.camera.core.CameraSelector
-import androidx.camera.integration.uiwidgets.rotations.UnlockedOrientationActivity.Companion.mCreated
-import androidx.test.core.app.ActivityScenario
+import android.app.Instrumentation
+import androidx.camera.core.CameraSelector.LENS_FACING_BACK
+import androidx.camera.core.CameraSelector.LENS_FACING_FRONT
import androidx.test.filters.LargeTest
+import androidx.test.platform.app.InstrumentationRegistry
import androidx.test.rule.GrantPermissionRule
-import androidx.testutils.withActivity
-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.Semaphore
-import java.util.concurrent.TimeUnit
@RunWith(Parameterized::class)
@LargeTest
class ImageAnalysisUnlockedOrientationTest(
private val lensFacing: Int,
- private val orientation: Int
+ private val rotation: RotationUnlocked,
+ private val testName: String
) : ImageAnalysisBaseTest<UnlockedOrientationActivity>() {
companion object {
- private val ORIENTATION_MAP = hashMapOf(
- SCREEN_ORIENTATION_PORTRAIT to Configuration.ORIENTATION_PORTRAIT,
- SCREEN_ORIENTATION_REVERSE_PORTRAIT to Configuration.ORIENTATION_PORTRAIT,
- SCREEN_ORIENTATION_LANDSCAPE to Configuration.ORIENTATION_LANDSCAPE,
- SCREEN_ORIENTATION_REVERSE_LANDSCAPE to Configuration.ORIENTATION_LANDSCAPE
- )
-
@JvmStatic
- @Parameterized.Parameters(name = "lensFacing={0}, orientation={1}")
+ @Parameterized.Parameters(name = "{2}")
fun data() = mutableListOf<Array<Any?>>().apply {
- add(arrayOf(CameraSelector.LENS_FACING_BACK, SCREEN_ORIENTATION_PORTRAIT))
- add(arrayOf(CameraSelector.LENS_FACING_BACK, SCREEN_ORIENTATION_REVERSE_PORTRAIT))
- add(arrayOf(CameraSelector.LENS_FACING_BACK, SCREEN_ORIENTATION_LANDSCAPE))
- add(arrayOf(CameraSelector.LENS_FACING_BACK, SCREEN_ORIENTATION_REVERSE_LANDSCAPE))
- add(arrayOf(CameraSelector.LENS_FACING_FRONT, SCREEN_ORIENTATION_PORTRAIT))
- add(arrayOf(CameraSelector.LENS_FACING_FRONT, SCREEN_ORIENTATION_REVERSE_PORTRAIT))
- add(arrayOf(CameraSelector.LENS_FACING_FRONT, SCREEN_ORIENTATION_LANDSCAPE))
- add(arrayOf(CameraSelector.LENS_FACING_FRONT, SCREEN_ORIENTATION_REVERSE_LANDSCAPE))
+ add(arrayOf(LENS_FACING_BACK, RotationUnlocked.Natural, "Back lens - Natural"))
+ add(arrayOf(LENS_FACING_BACK, RotationUnlocked.Left, "Back lens - Left"))
+ add(arrayOf(LENS_FACING_BACK, RotationUnlocked.Right, "Back lens - Right"))
+ add(arrayOf(LENS_FACING_FRONT, RotationUnlocked.Natural, "Front lens - Natural"))
+ add(arrayOf(LENS_FACING_FRONT, RotationUnlocked.Left, "Front lens - Left"))
+ add(arrayOf(LENS_FACING_FRONT, RotationUnlocked.Right, "Front lens - Right"))
}
}
@@ -83,25 +67,28 @@
@Test
fun verifyRotation() {
verifyRotation<UnlockedOrientationActivity>(lensFacing) {
- if (rotate(orientation)) {
-
- // Wait for the rotation to occur
- waitForRotation()
+ if (rotation.shouldRotate) {
+ rotateDeviceAndWait()
}
}
}
- private fun ActivityScenario<UnlockedOrientationActivity>.rotate(orientation: Int): Boolean {
- val currentOrientation = withActivity { resources.configuration.orientation }
- val didRotate = ORIENTATION_MAP[orientation] != currentOrientation
- mCreated = Semaphore(0)
- onActivity { activity ->
- activity.requestedOrientation = orientation
- }
- return didRotate
- }
+ private fun rotateDeviceAndWait() {
+ val monitor = Instrumentation.ActivityMonitor(
+ UnlockedOrientationActivity::class.java.name,
+ null,
+ false
+ )
+ InstrumentationRegistry.getInstrumentation().addMonitor(monitor)
- private fun waitForRotation() {
- assertThat(mCreated.tryAcquire(5, TimeUnit.SECONDS)).isTrue()
+ // Rotate
+ rotation.rotate(mDevice)
+
+ // Wait for the activity to be recreated after rotation
+ InstrumentationRegistry.getInstrumentation().waitForMonitorWithTimeout(
+ monitor,
+ 2000L
+ )
+ InstrumentationRegistry.getInstrumentation().waitForIdleSync()
}
}
diff --git a/camera/integration-tests/uiwidgetstestapp/src/androidTest/java/androidx/camera/integration/uiwidgets/rotations/ImageCaptureBaseTest.kt b/camera/integration-tests/uiwidgetstestapp/src/androidTest/java/androidx/camera/integration/uiwidgets/rotations/ImageCaptureBaseTest.kt
index 65fdb18a..f0709a4 100644
--- a/camera/integration-tests/uiwidgetstestapp/src/androidTest/java/androidx/camera/integration/uiwidgets/rotations/ImageCaptureBaseTest.kt
+++ b/camera/integration-tests/uiwidgetstestapp/src/androidTest/java/androidx/camera/integration/uiwidgets/rotations/ImageCaptureBaseTest.kt
@@ -47,6 +47,9 @@
*/
abstract class ImageCaptureBaseTest<A : CameraActivity> {
+ protected val mDevice: UiDevice =
+ UiDevice.getInstance(InstrumentationRegistry.getInstrumentation())
+
protected fun setUp(lensFacing: Int) {
// TODO(b/147448711) Cuttlefish seems to have an issue handling rotation. Might be
// related to the attached bug.
@@ -60,13 +63,11 @@
// Clear the device's UI and ensure it's in a natural orientation
CoreAppTestUtil.clearDeviceUI(InstrumentationRegistry.getInstrumentation())
- val device = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation())
- device.setOrientationNatural()
+ mDevice.setOrientationNatural()
}
protected fun tearDown() {
- val device = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation())
- device.unfreezeRotation()
+ mDevice.unfreezeRotation()
}
protected inline fun <reified A : CameraActivity> verifyRotation(
diff --git a/camera/integration-tests/uiwidgetstestapp/src/androidTest/java/androidx/camera/integration/uiwidgets/rotations/ImageCaptureUnlockedOrientationTest.kt b/camera/integration-tests/uiwidgetstestapp/src/androidTest/java/androidx/camera/integration/uiwidgets/rotations/ImageCaptureUnlockedOrientationTest.kt
index 10ec2b4..95bcbf0 100644
--- a/camera/integration-tests/uiwidgetstestapp/src/androidTest/java/androidx/camera/integration/uiwidgets/rotations/ImageCaptureUnlockedOrientationTest.kt
+++ b/camera/integration-tests/uiwidgetstestapp/src/androidTest/java/androidx/camera/integration/uiwidgets/rotations/ImageCaptureUnlockedOrientationTest.kt
@@ -16,47 +16,69 @@
package androidx.camera.integration.uiwidgets.rotations
-import android.content.pm.ActivityInfo
-import android.content.res.Configuration
-import androidx.camera.integration.uiwidgets.rotations.UnlockedOrientationActivity.Companion.mCreated
-import androidx.test.core.app.ActivityScenario
+import android.app.Instrumentation
+import androidx.camera.core.CameraSelector
+import androidx.camera.integration.uiwidgets.rotations.CameraActivity.Companion.IMAGE_CAPTURE_MODE_FILE
+import androidx.camera.integration.uiwidgets.rotations.CameraActivity.Companion.IMAGE_CAPTURE_MODE_IN_MEMORY
+import androidx.camera.integration.uiwidgets.rotations.CameraActivity.Companion.IMAGE_CAPTURE_MODE_MEDIA_STORE
+import androidx.camera.integration.uiwidgets.rotations.CameraActivity.Companion.IMAGE_CAPTURE_MODE_OUTPUT_STREAM
import androidx.test.filters.LargeTest
+import androidx.test.platform.app.InstrumentationRegistry
import androidx.test.rule.GrantPermissionRule
-import androidx.testutils.withActivity
-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.Semaphore
-import java.util.concurrent.TimeUnit
@RunWith(Parameterized::class)
@LargeTest
class ImageCaptureUnlockedOrientationTest(
private val lensFacing: Int,
- private val orientation: Int,
- private val captureMode: Int
+ private val captureMode: Int,
+ private val rotation: RotationUnlocked,
+ private val testName: String
) : ImageCaptureBaseTest<UnlockedOrientationActivity>() {
companion object {
- val ORIENTATION_MAP = hashMapOf(
- ActivityInfo.SCREEN_ORIENTATION_PORTRAIT to Configuration.ORIENTATION_PORTRAIT,
- ActivityInfo.SCREEN_ORIENTATION_REVERSE_PORTRAIT to Configuration.ORIENTATION_PORTRAIT,
- ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE to Configuration.ORIENTATION_LANDSCAPE,
- ActivityInfo.SCREEN_ORIENTATION_REVERSE_LANDSCAPE to Configuration.ORIENTATION_LANDSCAPE
- )
-
@JvmStatic
- @Parameterized.Parameters(name = "lensFacing={0}, orientation={1}, captureMode={2}")
+ @Parameterized.Parameters(name = "{3}")
fun data() = mutableListOf<Array<Any?>>().apply {
lensFacing.forEach { lens ->
- ORIENTATION_MAP.keys.forEach { orientation ->
- captureModes.forEach { mode ->
- add(arrayOf(lens, orientation, mode))
+ captureModes.forEach { mode ->
+ val lensName = if (lens == CameraSelector.LENS_FACING_BACK) {
+ "Back lens"
+ } else {
+ "Front lens"
}
+
+ val captureModeName = when (mode) {
+ IMAGE_CAPTURE_MODE_IN_MEMORY -> "In memory"
+ IMAGE_CAPTURE_MODE_FILE -> "File"
+ IMAGE_CAPTURE_MODE_OUTPUT_STREAM -> "Output stream"
+ IMAGE_CAPTURE_MODE_MEDIA_STORE -> "Media store"
+ else -> "Invalid capture mode"
+ }
+
+ add(
+ arrayOf(
+ lens, mode, RotationUnlocked.Natural,
+ "$lensName - $captureModeName - Natural"
+ )
+ )
+ add(
+ arrayOf(
+ lens, mode, RotationUnlocked.Left,
+ "$lensName - $captureModeName - Left"
+ )
+ )
+ add(
+ arrayOf(
+ lens, mode, RotationUnlocked.Right,
+ "$lensName - $captureModeName - Right"
+ )
+ )
}
}
}
@@ -79,25 +101,28 @@
@Test
fun verifyRotation() {
verifyRotation<UnlockedOrientationActivity>(lensFacing, captureMode) {
- if (rotate(orientation)) {
-
- // Wait for the rotation to occur
- waitForRotation()
+ if (rotation.shouldRotate) {
+ rotateDeviceAndWait()
}
}
}
- private fun ActivityScenario<UnlockedOrientationActivity>.rotate(orientation: Int): Boolean {
- val currentOrientation = withActivity { resources.configuration.orientation }
- val didRotate = ORIENTATION_MAP[orientation] != currentOrientation
- mCreated = Semaphore(0)
- onActivity { activity ->
- activity.requestedOrientation = orientation
- }
- return didRotate
- }
+ private fun rotateDeviceAndWait() {
+ val monitor = Instrumentation.ActivityMonitor(
+ UnlockedOrientationActivity::class.java.name,
+ null,
+ false
+ )
+ InstrumentationRegistry.getInstrumentation().addMonitor(monitor)
- private fun waitForRotation() {
- assertThat(mCreated.tryAcquire(5, TimeUnit.SECONDS)).isTrue()
+ // Rotate
+ rotation.rotate(mDevice)
+
+ // Wait for the activity to be recreated after rotation
+ InstrumentationRegistry.getInstrumentation().waitForMonitorWithTimeout(
+ monitor,
+ 2000L
+ )
+ InstrumentationRegistry.getInstrumentation().waitForIdleSync()
}
}
diff --git a/camera/integration-tests/uiwidgetstestapp/src/androidTest/java/androidx/camera/integration/uiwidgets/rotations/RotationUnlocked.kt b/camera/integration-tests/uiwidgetstestapp/src/androidTest/java/androidx/camera/integration/uiwidgets/rotations/RotationUnlocked.kt
new file mode 100644
index 0000000..31e8fc6
--- /dev/null
+++ b/camera/integration-tests/uiwidgetstestapp/src/androidTest/java/androidx/camera/integration/uiwidgets/rotations/RotationUnlocked.kt
@@ -0,0 +1,29 @@
+/*
+ * Copyright 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.camera.integration.uiwidgets.rotations
+
+import androidx.test.uiautomator.UiDevice
+
+/**
+ * Defines how to rotate in the context of an activity with an unlocked orientation. It assumes
+ * the test has been set up so that the device is starting in a natural orientation.
+ */
+sealed class RotationUnlocked(val shouldRotate: Boolean, val rotate: (UiDevice) -> Unit) {
+ object Natural : RotationUnlocked(false, {})
+ object Left : RotationUnlocked(true, UiDevice::setOrientationLeft)
+ object Right : RotationUnlocked(true, UiDevice::setOrientationRight)
+}
\ No newline at end of file
diff --git a/camera/integration-tests/uiwidgetstestapp/src/main/java/androidx/camera/integration/uiwidgets/rotations/UnlockedOrientationActivity.kt b/camera/integration-tests/uiwidgetstestapp/src/main/java/androidx/camera/integration/uiwidgets/rotations/UnlockedOrientationActivity.kt
index 0ae778a..ec1b7fe 100644
--- a/camera/integration-tests/uiwidgetstestapp/src/main/java/androidx/camera/integration/uiwidgets/rotations/UnlockedOrientationActivity.kt
+++ b/camera/integration-tests/uiwidgetstestapp/src/main/java/androidx/camera/integration/uiwidgets/rotations/UnlockedOrientationActivity.kt
@@ -16,19 +16,4 @@
package androidx.camera.integration.uiwidgets.rotations
-import android.os.Bundle
-import java.util.concurrent.Semaphore
-
-class UnlockedOrientationActivity : CameraActivity() {
-
- override fun onCreate(savedInstanceState: Bundle?) {
- super.onCreate(savedInstanceState)
- mCreated.release()
- }
-
- // region For testing
- companion object {
- var mCreated = Semaphore(0)
- }
- // endregion
-}
\ No newline at end of file
+class UnlockedOrientationActivity : CameraActivity()
\ No newline at end of file
diff --git a/compose/animation/animation-core/build.gradle b/compose/animation/animation-core/build.gradle
index 78e8313..83e859d 100644
--- a/compose/animation/animation-core/build.gradle
+++ b/compose/animation/animation-core/build.gradle
@@ -70,7 +70,7 @@
androidx {
name = "Compose Animation Core"
publish = Publish.SNAPSHOT_AND_RELEASE
- mavenVersion = LibraryVersions.UI
+ mavenVersion = LibraryVersions.COMPOSE
mavenGroup = LibraryGroups.Compose.ANIMATION
inceptionYear = "2019"
description = "Animation engine and animation primitives that are the building blocks of the Compose animation library"
diff --git a/compose/animation/animation/build.gradle b/compose/animation/animation/build.gradle
index ca914a2..bcea23f 100644
--- a/compose/animation/animation/build.gradle
+++ b/compose/animation/animation/build.gradle
@@ -72,7 +72,7 @@
androidx {
name = "Compose Animation"
publish = Publish.SNAPSHOT_AND_RELEASE
- mavenVersion = LibraryVersions.UI
+ mavenVersion = LibraryVersions.COMPOSE
mavenGroup = LibraryGroups.Compose.ANIMATION
inceptionYear = "2019"
description = "Compose animation library"
diff --git a/compose/compose-compiler-hosted/integration-tests/src/test/java/androidx/compose/compiler/plugins/kotlin/LiveLiteralCodegenTests.kt b/compose/compose-compiler-hosted/integration-tests/src/test/java/androidx/compose/compiler/plugins/kotlin/LiveLiteralCodegenTests.kt
index 4886776..60526ec 100644
--- a/compose/compose-compiler-hosted/integration-tests/src/test/java/androidx/compose/compiler/plugins/kotlin/LiveLiteralCodegenTests.kt
+++ b/compose/compose-compiler-hosted/integration-tests/src/test/java/androidx/compose/compiler/plugins/kotlin/LiveLiteralCodegenTests.kt
@@ -20,6 +20,7 @@
import androidx.compose.runtime.InternalComposeApi
import androidx.compose.runtime.internal.updateLiveLiteralValue
import org.jetbrains.kotlin.config.CompilerConfiguration
+import org.junit.Ignore
import org.junit.Test
import org.junit.runner.RunWith
import org.robolectric.annotation.Config
@@ -37,6 +38,7 @@
configuration.put(ComposeConfiguration.LIVE_LITERALS_ENABLED_KEY, true)
}
+ @Ignore
@Test
fun testBasicFunctionality(): Unit = ensureSetup {
compose("""
@@ -57,6 +59,7 @@
}
}
+ @Ignore
@Test
fun testObjectFieldsLoweredToStaticFields(): Unit = ensureSetup {
validateBytecode("""
diff --git a/compose/compose-compiler-hosted/src/main/java/androidx/compose/compiler/plugins/kotlin/ComposeIrGenerationExtension.kt b/compose/compose-compiler-hosted/src/main/java/androidx/compose/compiler/plugins/kotlin/ComposeIrGenerationExtension.kt
index 02fa29d..c7ef8be4 100644
--- a/compose/compose-compiler-hosted/src/main/java/androidx/compose/compiler/plugins/kotlin/ComposeIrGenerationExtension.kt
+++ b/compose/compose-compiler-hosted/src/main/java/androidx/compose/compiler/plugins/kotlin/ComposeIrGenerationExtension.kt
@@ -20,8 +20,6 @@
import androidx.compose.compiler.plugins.kotlin.lower.ComposerIntrinsicTransformer
import androidx.compose.compiler.plugins.kotlin.lower.ComposerLambdaMemoization
import androidx.compose.compiler.plugins.kotlin.lower.ComposerParamTransformer
-import androidx.compose.compiler.plugins.kotlin.lower.DurableKeyVisitor
-import androidx.compose.compiler.plugins.kotlin.lower.LiveLiteralTransformer
import org.jetbrains.kotlin.backend.common.extensions.IrGenerationExtension
import org.jetbrains.kotlin.backend.common.extensions.IrPluginContext
import org.jetbrains.kotlin.backend.common.extensions.IrPluginContextImpl
@@ -32,7 +30,7 @@
import org.jetbrains.kotlin.resolve.DelegatingBindingTrace
class ComposeIrGenerationExtension(
- private val liveLiteralsEnabled: Boolean = false,
+ @Suppress("unused") private val liveLiteralsEnabled: Boolean = false,
private val sourceInformationEnabled: Boolean = true
) : IrGenerationExtension {
override fun generate(
@@ -49,13 +47,15 @@
// create a symbol remapper to be used across all transforms
val symbolRemapper = DeepCopySymbolRemapper()
- LiveLiteralTransformer(
- liveLiteralsEnabled,
- DurableKeyVisitor(),
- pluginContext,
- symbolRemapper,
- bindingTrace
- ).lower(moduleFragment)
+ // Temporarily Disabled
+ // See b/166516111 and b/163807311
+// LiveLiteralTransformer(
+// liveLiteralsEnabled,
+// DurableKeyVisitor(),
+// pluginContext,
+// symbolRemapper,
+// bindingTrace
+// ).lower(moduleFragment)
// Memoize normal lambdas and wrap composable lambdas
ComposerLambdaMemoization(pluginContext, symbolRemapper, bindingTrace).lower(moduleFragment)
diff --git a/compose/desktop/desktop/samples/src/jvmMain/kotlin/androidx/compose/desktop/examples/example1/Main.kt b/compose/desktop/desktop/samples/src/jvmMain/kotlin/androidx/compose/desktop/examples/example1/Main.kt
index 64d694b..a44338e 100644
--- a/compose/desktop/desktop/samples/src/jvmMain/kotlin/androidx/compose/desktop/examples/example1/Main.kt
+++ b/compose/desktop/desktop/samples/src/jvmMain/kotlin/androidx/compose/desktop/examples/example1/Main.kt
@@ -25,7 +25,11 @@
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.preferredHeight
+import androidx.compose.foundation.layout.width
import androidx.compose.foundation.layout.wrapContentSize
+import androidx.compose.foundation.lazy.ExperimentalLazyDsl
+import androidx.compose.foundation.lazy.LazyColumn
+import androidx.compose.foundation.lazy.LazyRow
import androidx.compose.material.Button
import androidx.compose.material.CircularProgressIndicator
import androidx.compose.material.ExtendedFloatingActionButton
@@ -33,6 +37,7 @@
import androidx.compose.material.Slider
import androidx.compose.material.TextField
import androidx.compose.material.TopAppBar
+import androidx.compose.runtime.Composable
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
@@ -68,97 +73,120 @@
)
},
bodyContent = {
- val amount = remember { mutableStateOf(0) }
- val animation = remember { mutableStateOf(true) }
- val text = remember {
- mutableStateOf("Hello \uD83E\uDDD1\uD83C\uDFFF\u200D\uD83E\uDDB0\nПривет")
- }
- Column(Modifier.fillMaxSize(), Arrangement.SpaceEvenly) {
- Text(
- text = "Привет! 你好! Desktop Compose ${amount.value}",
- color = Color.Black,
- modifier = Modifier
- .background(Color.Blue)
- .preferredHeight(56.dp)
- .wrapContentSize(Alignment.Center)
- )
-
- Text(
- text = with(AnnotatedString.Builder("The quick ")) {
- pushStyle(SpanStyle(color = Color(0xff964B00)))
- append("brown fox")
- pop()
- append(" 🦊 ate a ")
- pushStyle(SpanStyle(fontSize = 30.sp))
- append("zesty hamburgerfons")
- pop()
- append(" 🍔.\nThe 👩👩👧👧 laughed.")
- addStyle(SpanStyle(color = Color.Green), 25, 35)
- toAnnotatedString()
- },
- color = Color.Black
- )
-
- Text(
- text = "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do" +
- " eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad" +
- " minim veniam, quis nostrud exercitation ullamco laboris nisi ut" +
- " aliquipex ea commodo consequat. Duis aute irure dolor in reprehenderit" +
- " in voluptate velit esse cillum dolore eu fugiat nulla pariatur." +
- " Excepteur" +
- " sint occaecat cupidatat non proident, sunt in culpa qui officia" +
- " deserunt mollit anim id est laborum."
- )
-
- Text(
- text = "fun <T : Comparable<T>> List<T>.quickSort(): List<T> = when {\n" +
- " size < 2 -> this\n" +
- " else -> {\n" +
- " val pivot = first()\n" +
- " val (smaller, greater) = drop(1).partition { it <= pivot }\n" +
- " smaller.quickSort() + pivot + greater.quickSort()\n" +
- " }\n" +
- "}",
- modifier = Modifier.padding(10.dp),
- fontFamily = italicFont
- )
-
- Button(onClick = {
- amount.value++
- }) {
- Text("Base")
- }
-
- Row(modifier = Modifier.padding(vertical = 10.dp),
- verticalGravity = Alignment.CenterVertically) {
- Button(
- onClick = {
- animation.value = !animation.value
- }) {
- Text("Toggle")
- }
-
- if (animation.value) {
- CircularProgressIndicator()
- }
- }
-
- Slider(value = amount.value.toFloat() / 100f,
- onValueChange = { amount.value = (it * 100).toInt() })
- TextField(
- value = amount.value.toString(),
- onValueChange = { amount.value = it.toIntOrNull() ?: 42 },
- label = { Text(text = "Input1") }
- )
- TextField(
- value = text.value,
- onValueChange = { text.value = it },
- label = { Text(text = "Input2") }
- )
-
- Image(imageResource("androidx.compose.desktop/example/circus.jpg"))
+ Row {
+ LeftColumn(Modifier.weight(1f))
+ RightColumn(Modifier.width(200.dp))
}
}
)
}
}
+
+@Composable
+private fun LeftColumn(modifier: Modifier) = Column(modifier) {
+ val amount = remember { mutableStateOf(0) }
+ val animation = remember { mutableStateOf(true) }
+ val text = remember {
+ mutableStateOf("Hello \uD83E\uDDD1\uD83C\uDFFF\u200D\uD83E\uDDB0\nПривет")
+ }
+ Column(Modifier.fillMaxSize(), Arrangement.SpaceEvenly) {
+ Text(
+ text = "Привет! 你好! Desktop Compose ${amount.value}",
+ color = Color.Black,
+ modifier = Modifier
+ .background(Color.Blue)
+ .preferredHeight(56.dp)
+ .wrapContentSize(Alignment.Center)
+ )
+
+ Text(
+ text = with(AnnotatedString.Builder("The quick ")) {
+ pushStyle(SpanStyle(color = Color(0xff964B00)))
+ append("brown fox")
+ pop()
+ append(" 🦊 ate a ")
+ pushStyle(SpanStyle(fontSize = 30.sp))
+ append("zesty hamburgerfons")
+ pop()
+ append(" 🍔.\nThe 👩👩👧👧 laughed.")
+ addStyle(SpanStyle(color = Color.Green), 25, 35)
+ toAnnotatedString()
+ },
+ color = Color.Black
+ )
+
+ Text(
+ text = "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do" +
+ " eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad" +
+ " minim veniam, quis nostrud exercitation ullamco laboris nisi ut" +
+ " aliquipex ea commodo consequat. Duis aute irure dolor in reprehenderit" +
+ " in voluptate velit esse cillum dolore eu fugiat nulla pariatur." +
+ " Excepteur" +
+ " sint occaecat cupidatat non proident, sunt in culpa qui officia" +
+ " deserunt mollit anim id est laborum."
+ )
+
+ Text(
+ text = "fun <T : Comparable<T>> List<T>.quickSort(): List<T> = when {\n" +
+ " size < 2 -> this\n" +
+ " else -> {\n" +
+ " val pivot = first()\n" +
+ " val (smaller, greater) = drop(1).partition { it <= pivot }\n" +
+ " smaller.quickSort() + pivot + greater.quickSort()\n" +
+ " }\n" +
+ "}",
+ modifier = Modifier.padding(10.dp),
+ fontFamily = italicFont
+ )
+
+ Button(onClick = {
+ amount.value++
+ }) {
+ Text("Base")
+ }
+
+ Row(
+ modifier = Modifier.padding(vertical = 10.dp),
+ verticalGravity = Alignment.CenterVertically
+ ) {
+ Button(
+ onClick = {
+ animation.value = !animation.value
+ }) {
+ Text("Toggle")
+ }
+
+ if (animation.value) {
+ CircularProgressIndicator()
+ }
+ }
+
+ Slider(value = amount.value.toFloat() / 100f,
+ onValueChange = { amount.value = (it * 100).toInt() })
+ TextField(
+ value = amount.value.toString(),
+ onValueChange = { amount.value = it.toIntOrNull() ?: 42 },
+ label = { Text(text = "Input1") }
+ )
+ TextField(
+ value = text.value,
+ onValueChange = { text.value = it },
+ label = { Text(text = "Input2") }
+ )
+
+ Image(imageResource("androidx/compose/desktop/example/circus.jpg"))
+ }
+}
+
+@OptIn(ExperimentalLazyDsl::class)
+@Composable
+private fun RightColumn(modifier: Modifier) = LazyColumn(modifier) {
+ items((1..100).toList()) { x ->
+ LazyRow {
+ items((0..23).toList()) { y ->
+ val str = if (y == 0) x.toString() else ('a' + y).toString()
+ Text("$str ")
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/compose/desktop/desktop/src/jvmMain/kotlin/androidx/compose/desktop/SkiaWindow.kt b/compose/desktop/desktop/src/jvmMain/kotlin/androidx/compose/desktop/ComposeWindow.kt
similarity index 84%
rename from compose/desktop/desktop/src/jvmMain/kotlin/androidx/compose/desktop/SkiaWindow.kt
rename to compose/desktop/desktop/src/jvmMain/kotlin/androidx/compose/desktop/ComposeWindow.kt
index aa93254..ddf8d6b 100644
--- a/compose/desktop/desktop/src/jvmMain/kotlin/androidx/compose/desktop/SkiaWindow.kt
+++ b/compose/desktop/desktop/src/jvmMain/kotlin/androidx/compose/desktop/ComposeWindow.kt
@@ -16,6 +16,9 @@
package androidx.compose.desktop
import androidx.compose.runtime.dispatch.DesktopUiDispatcher
+import androidx.compose.ui.gesture.scrollorientationlocking.Orientation
+import androidx.compose.ui.input.mouse.MouseScrollEvent
+import androidx.compose.ui.input.mouse.MouseScrollUnit
import androidx.compose.ui.platform.DesktopOwners
import org.jetbrains.skija.Canvas
import org.jetbrains.skiko.SkiaLayer
@@ -29,6 +32,7 @@
import java.awt.event.MouseAdapter
import java.awt.event.MouseEvent
import java.awt.event.MouseMotionAdapter
+import java.awt.event.MouseWheelEvent
import javax.swing.JFrame
class ComposeWindow : JFrame {
@@ -108,6 +112,11 @@
owners?.onMouseDragged(event.x, event.y)
}
})
+ layer.addMouseWheelListener { event ->
+ DesktopUiDispatcher.Dispatcher.lockCallbacks {
+ owners?.onMouseScroll(event.x, event.y, event.toComposeEvent())
+ }
+ }
layer.addKeyListener(object : KeyAdapter() {
override fun keyPressed(
event: KeyEvent
@@ -154,6 +163,21 @@
}
}
+private fun MouseWheelEvent.toComposeEvent() = MouseScrollEvent(
+ delta = if (scrollType == MouseWheelEvent.WHEEL_BLOCK_SCROLL) {
+ MouseScrollUnit.Page((scrollAmount * preciseWheelRotation).toFloat())
+ } else {
+ MouseScrollUnit.Line((scrollAmount * preciseWheelRotation).toFloat())
+ },
+
+ // There are no other way to detect horizontal scrolling in AWT
+ orientation = if (isShiftDown) {
+ Orientation.Horizontal
+ } else {
+ Orientation.Vertical
+ }
+)
+
// Simple FPS tracker for debug purposes
internal class FPSTracker {
private var t0 = 0L
diff --git a/compose/desktop/desktop/src/jvmMain/kotlin/androidx/compose/desktop/test/TestSkiaWindow.kt b/compose/desktop/desktop/src/jvmMain/kotlin/androidx/compose/desktop/test/TestSkiaWindow.kt
index 589cfdc..fddf599 100644
--- a/compose/desktop/desktop/src/jvmMain/kotlin/androidx/compose/desktop/test/TestSkiaWindow.kt
+++ b/compose/desktop/desktop/src/jvmMain/kotlin/androidx/compose/desktop/test/TestSkiaWindow.kt
@@ -24,6 +24,8 @@
import org.jetbrains.skija.Surface
import java.awt.Component
+// TODO(demin): replace by androidx.compose.ui.test.TestComposeWindow when it will be
+// in :ui:ui-test module
class TestSkiaWindow(
val width: Int,
val height: Int
diff --git a/compose/desktop/desktop/src/jvmTest/kotlin/androidx/compose/ui/graphics/canvas/DesktopCanvasTest.kt b/compose/desktop/desktop/src/jvmTest/kotlin/androidx/compose/ui/graphics/canvas/DesktopCanvasTest.kt
index dcf7583..40ec7ffd 100644
--- a/compose/desktop/desktop/src/jvmTest/kotlin/androidx/compose/ui/graphics/canvas/DesktopCanvasTest.kt
+++ b/compose/desktop/desktop/src/jvmTest/kotlin/androidx/compose/ui/graphics/canvas/DesktopCanvasTest.kt
@@ -26,7 +26,7 @@
import androidx.compose.ui.graphics.Paint
import androidx.compose.ui.graphics.StrokeCap
import androidx.compose.ui.graphics.imageFromResource
-import androidx.compose.ui.graphics.vectormath.Matrix4
+import androidx.compose.ui.graphics.Matrix
import androidx.compose.ui.graphics.withSave
import androidx.compose.ui.graphics.withSaveLayer
import androidx.compose.ui.unit.IntOffset
@@ -244,7 +244,7 @@
canvas.withSave {
canvas.concat(
- Matrix4.identity().apply {
+ Matrix().apply {
translate(12f, 2f)
}
)
diff --git a/compose/foundation/foundation-layout/build.gradle b/compose/foundation/foundation-layout/build.gradle
index 96eb0d5..93e2980 100644
--- a/compose/foundation/foundation-layout/build.gradle
+++ b/compose/foundation/foundation-layout/build.gradle
@@ -76,7 +76,7 @@
androidx {
name = "Compose Layouts"
publish = Publish.SNAPSHOT_AND_RELEASE
- mavenVersion = LibraryVersions.UI
+ mavenVersion = LibraryVersions.COMPOSE
mavenGroup = LibraryGroups.Compose.FOUNDATION
inceptionYear = "2019"
description = "Compose layout implementations"
diff --git a/compose/foundation/foundation-text/build.gradle b/compose/foundation/foundation-text/build.gradle
index eafc479..a88b8d4 100644
--- a/compose/foundation/foundation-text/build.gradle
+++ b/compose/foundation/foundation-text/build.gradle
@@ -83,7 +83,7 @@
androidx {
name = "Compose Text"
publish = Publish.SNAPSHOT_AND_RELEASE
- mavenVersion = LibraryVersions.UI
+ mavenVersion = LibraryVersions.COMPOSE
mavenGroup = LibraryGroups.Compose.FOUNDATION
inceptionYear = "2020"
description = "Compose Text higher level APIs"
diff --git a/compose/foundation/foundation-text/src/commonMain/kotlin/androidx/compose/foundation/text/CoreTextField.kt b/compose/foundation/foundation-text/src/commonMain/kotlin/androidx/compose/foundation/text/CoreTextField.kt
index 112dd17..5548cda 100644
--- a/compose/foundation/foundation-text/src/commonMain/kotlin/androidx/compose/foundation/text/CoreTextField.kt
+++ b/compose/foundation/foundation-text/src/commonMain/kotlin/androidx/compose/foundation/text/CoreTextField.kt
@@ -208,7 +208,7 @@
state.processor,
keyboardType,
imeAction,
- onValueChangeWrapper,
+ { manager.onValueChange(it) },
onImeActionPerformed
)
if (state.inputSession != NO_SESSION && textInputService != null) {
diff --git a/compose/foundation/foundation/api/current.txt b/compose/foundation/foundation/api/current.txt
index d778afb..87f275e 100644
--- a/compose/foundation/foundation/api/current.txt
+++ b/compose/foundation/foundation/api/current.txt
@@ -224,6 +224,9 @@
package androidx.compose.foundation.gestures {
+ public final class AndroidScrollableKt {
+ }
+
public final class DraggableKt {
method public static androidx.compose.ui.Modifier draggable(androidx.compose.ui.Modifier, androidx.compose.ui.gesture.scrollorientationlocking.Orientation orientation, boolean enabled = true, boolean reverseDirection = false, androidx.compose.foundation.InteractionState? interactionState = null, boolean startDragImmediately = false, kotlin.jvm.functions.Function1<? super androidx.compose.ui.gesture.Direction,java.lang.Boolean> canDrag = { return enabled }, kotlin.jvm.functions.Function1<? super androidx.compose.ui.geometry.Offset,kotlin.Unit> onDragStarted = {}, kotlin.jvm.functions.Function1<? super java.lang.Float,kotlin.Unit> onDragStopped = {}, kotlin.jvm.functions.Function2<? super androidx.compose.ui.unit.Density,? super java.lang.Float,kotlin.Unit> onDrag);
}
diff --git a/compose/foundation/foundation/api/public_plus_experimental_current.txt b/compose/foundation/foundation/api/public_plus_experimental_current.txt
index d778afb..87f275e 100644
--- a/compose/foundation/foundation/api/public_plus_experimental_current.txt
+++ b/compose/foundation/foundation/api/public_plus_experimental_current.txt
@@ -224,6 +224,9 @@
package androidx.compose.foundation.gestures {
+ public final class AndroidScrollableKt {
+ }
+
public final class DraggableKt {
method public static androidx.compose.ui.Modifier draggable(androidx.compose.ui.Modifier, androidx.compose.ui.gesture.scrollorientationlocking.Orientation orientation, boolean enabled = true, boolean reverseDirection = false, androidx.compose.foundation.InteractionState? interactionState = null, boolean startDragImmediately = false, kotlin.jvm.functions.Function1<? super androidx.compose.ui.gesture.Direction,java.lang.Boolean> canDrag = { return enabled }, kotlin.jvm.functions.Function1<? super androidx.compose.ui.geometry.Offset,kotlin.Unit> onDragStarted = {}, kotlin.jvm.functions.Function1<? super java.lang.Float,kotlin.Unit> onDragStopped = {}, kotlin.jvm.functions.Function2<? super androidx.compose.ui.unit.Density,? super java.lang.Float,kotlin.Unit> onDrag);
}
diff --git a/compose/foundation/foundation/api/restricted_current.txt b/compose/foundation/foundation/api/restricted_current.txt
index d778afb..87f275e 100644
--- a/compose/foundation/foundation/api/restricted_current.txt
+++ b/compose/foundation/foundation/api/restricted_current.txt
@@ -224,6 +224,9 @@
package androidx.compose.foundation.gestures {
+ public final class AndroidScrollableKt {
+ }
+
public final class DraggableKt {
method public static androidx.compose.ui.Modifier draggable(androidx.compose.ui.Modifier, androidx.compose.ui.gesture.scrollorientationlocking.Orientation orientation, boolean enabled = true, boolean reverseDirection = false, androidx.compose.foundation.InteractionState? interactionState = null, boolean startDragImmediately = false, kotlin.jvm.functions.Function1<? super androidx.compose.ui.gesture.Direction,java.lang.Boolean> canDrag = { return enabled }, kotlin.jvm.functions.Function1<? super androidx.compose.ui.geometry.Offset,kotlin.Unit> onDragStarted = {}, kotlin.jvm.functions.Function1<? super java.lang.Float,kotlin.Unit> onDragStopped = {}, kotlin.jvm.functions.Function2<? super androidx.compose.ui.unit.Density,? super java.lang.Float,kotlin.Unit> onDrag);
}
diff --git a/compose/foundation/foundation/build.gradle b/compose/foundation/foundation/build.gradle
index b1333ac..364462c 100644
--- a/compose/foundation/foundation/build.gradle
+++ b/compose/foundation/foundation/build.gradle
@@ -79,13 +79,19 @@
exclude group: 'org.mockito' // to keep control on the mockito version
}
}
+
+ desktopTest.dependencies {
+ implementation(TRUTH)
+ implementation(JUNIT)
+ implementation(SKIKO_CURRENT_OS)
+ }
}
}
androidx {
name = "Compose Foundation"
publish = Publish.SNAPSHOT_AND_RELEASE
- mavenVersion = LibraryVersions.UI
+ mavenVersion = LibraryVersions.COMPOSE
mavenGroup = LibraryGroups.Compose.FOUNDATION
inceptionYear = "2018"
description = "Higher level abstractions of the Compose UI primitives. This library is design system agnostic, providing the high-level building blocks for both application and design-system developers"
diff --git a/compose/foundation/foundation/integration-tests/foundation-demos/src/main/java/androidx/compose/foundation/demos/FoundationDemos.kt b/compose/foundation/foundation/integration-tests/foundation-demos/src/main/java/androidx/compose/foundation/demos/FoundationDemos.kt
index 33d611e..6f8b2fe 100644
--- a/compose/foundation/foundation/integration-tests/foundation-demos/src/main/java/androidx/compose/foundation/demos/FoundationDemos.kt
+++ b/compose/foundation/foundation/integration-tests/foundation-demos/src/main/java/androidx/compose/foundation/demos/FoundationDemos.kt
@@ -17,7 +17,8 @@
package androidx.compose.foundation.demos
import androidx.compose.foundation.samples.ControlledScrollableRowSample
-import androidx.compose.foundation.samples.InteractionStateSample
+import androidx.compose.foundation.samples.MultipleInteractionStateSample
+import androidx.compose.foundation.samples.PriorityInteractionStateSample
import androidx.compose.foundation.samples.ScrollableColumnSample
import androidx.compose.integration.demos.common.ComposableDemo
import androidx.compose.integration.demos.common.DemoCategory
@@ -29,5 +30,6 @@
ComposableDemo("Draw Modifiers") { DrawModifiersDemo() },
ComposableDemo("Boxes") { BoxDemo() },
DemoCategory("Lazy lists", LazyListDemos),
- ComposableDemo("InteractionState") { InteractionStateSample() }
+ ComposableDemo("Priority InteractionState") { PriorityInteractionStateSample() },
+ ComposableDemo("Multiple-interaction InteractionState") { MultipleInteractionStateSample() }
))
diff --git a/compose/foundation/foundation/samples/src/main/java/androidx/compose/foundation/samples/InteractionStateSample.kt b/compose/foundation/foundation/samples/src/main/java/androidx/compose/foundation/samples/InteractionStateSample.kt
index 906b9fc..2ad737c 100644
--- a/compose/foundation/foundation/samples/src/main/java/androidx/compose/foundation/samples/InteractionStateSample.kt
+++ b/compose/foundation/foundation/samples/src/main/java/androidx/compose/foundation/samples/InteractionStateSample.kt
@@ -25,6 +25,8 @@
import androidx.compose.foundation.clickable
import androidx.compose.foundation.currentTextStyle
import androidx.compose.foundation.gestures.draggable
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.preferredSize
import androidx.compose.foundation.layout.wrapContentSize
@@ -38,7 +40,7 @@
@Sampled
@Composable
-fun InteractionStateSample() {
+fun PriorityInteractionStateSample() {
val interactionState = remember { InteractionState() }
val draggable = Modifier.draggable(
@@ -47,6 +49,8 @@
) { /* update some business state here */ }
// Use InteractionState to determine how this component should appear during transient UI states
+ // In this example we are using a 'priority' system, such that we ignore multiple states, and
+ // don't care about the most recent state - Dragged is more important than Pressed.
val (text, color) = when {
Interaction.Dragged in interactionState -> "Dragged" to Color.Red
Interaction.Pressed in interactionState -> "Pressed" to Color.Blue
@@ -73,4 +77,76 @@
)
}
}
-}
\ No newline at end of file
+}
+
+@Sampled
+@Composable
+fun MultipleInteractionStateSample() {
+ val interactionState = remember { InteractionState() }
+
+ val draggable = Modifier.draggable(
+ orientation = Orientation.Horizontal,
+ interactionState = interactionState
+ ) { /* update some business state here */ }
+
+ val clickable = Modifier.clickable(interactionState = interactionState) {
+ /* update some business state here */
+ }
+
+ // In this example we have a complex component that can be in multiple states at the same time
+ // (both pressed and dragged, since different areas of the same component can be pressed and
+ // dragged at the same time), and we want to use only the most recent state to show the visual
+ // state of the component. This could be with a visual overlay, or similar. Note that the most
+ // recent state is the _last_ state added to interactionState, so we want to start from the end,
+ // hence we use `lastOrNull` and not `firstOrNull`.
+ val latestState = interactionState.value.lastOrNull {
+ // We only care about pressed and dragged states here, so ignore everything else
+ it is Interaction.Dragged || it is Interaction.Pressed
+ }
+
+ val text = when (latestState) {
+ Interaction.Dragged -> "Dragged"
+ Interaction.Pressed -> "Pressed"
+ else -> "No state"
+ }
+
+ Column(
+ Modifier
+ .fillMaxSize()
+ .wrapContentSize()
+ ) {
+ Row {
+ Box(
+ Modifier
+ .preferredSize(width = 240.dp, height = 80.dp)
+ .then(clickable),
+ border = BorderStroke(3.dp, Color.Blue)
+ ) {
+ val pressed = Interaction.Pressed in interactionState
+ Text(
+ text = if (pressed) "Pressed" else "Not pressed",
+ style = currentTextStyle().copy(textAlign = TextAlign.Center),
+ modifier = Modifier.fillMaxSize().wrapContentSize()
+ )
+ }
+ Box(
+ Modifier
+ .preferredSize(width = 240.dp, height = 80.dp)
+ .then(draggable),
+ border = BorderStroke(3.dp, Color.Red)
+ ) {
+ val dragged = Interaction.Dragged in interactionState
+ Text(
+ text = if (dragged) "Dragged" else "Not dragged",
+ style = currentTextStyle().copy(textAlign = TextAlign.Center),
+ modifier = Modifier.fillMaxSize().wrapContentSize()
+ )
+ }
+ }
+ Text(
+ text = text,
+ style = currentTextStyle().copy(textAlign = TextAlign.Center),
+ modifier = Modifier.fillMaxSize().wrapContentSize()
+ )
+ }
+}
diff --git a/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/ScrollableTest.kt b/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/ScrollableTest.kt
index 1471e744..cb70f95 100644
--- a/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/ScrollableTest.kt
+++ b/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/ScrollableTest.kt
@@ -35,6 +35,7 @@
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.milliseconds
import androidx.test.filters.LargeTest
+import androidx.ui.test.ExperimentalTesting
import androidx.ui.test.TestUiDispatcher
import androidx.ui.test.center
import androidx.ui.test.createComposeRule
@@ -65,6 +66,7 @@
private val scrollableBoxTag = "scrollableBox"
@Test
+ @OptIn(ExperimentalTesting::class)
fun scrollable_horizontalScroll() = runBlockingWithManualClock { clock ->
var total = 0f
val controller = ScrollableController(
@@ -120,6 +122,7 @@
}
@Test
+ @OptIn(ExperimentalTesting::class)
fun scrollable_verticalScroll() = runBlockingWithManualClock { clock ->
var total = 0f
val controller = ScrollableController(
@@ -175,6 +178,7 @@
}
@Test
+ @OptIn(ExperimentalTesting::class)
fun scrollable_startStop_notify() = runBlockingWithManualClock(true) { clock ->
var startTrigger = 0f
var stopTrigger = 0f
@@ -220,6 +224,7 @@
}
@Test
+ @OptIn(ExperimentalTesting::class)
fun scrollable_disabledWontCallLambda() = runBlockingWithManualClock(true) { clock ->
val enabled = mutableStateOf(true)
var total = 0f
@@ -265,6 +270,7 @@
}
@Test
+ @OptIn(ExperimentalTesting::class)
fun scrollable_velocityProxy() = runBlockingWithManualClock { clock ->
var velocityTriggered = 0f
var total = 0f
@@ -312,6 +318,7 @@
}
@Test
+ @OptIn(ExperimentalTesting::class)
fun scrollable_startWithoutSlop_ifFlinging() = runBlockingWithManualClock { clock ->
var total = 0f
val controller = ScrollableController(
@@ -355,6 +362,7 @@
}
@Test
+ @OptIn(ExperimentalTesting::class)
fun scrollable_cancel_callsDragStop() = runBlockingWithManualClock { clock ->
var total by mutableStateOf(0f)
var dragStopped = 0f
@@ -393,6 +401,7 @@
}
@Test
+ @OptIn(ExperimentalTesting::class)
fun scrollable_snappingScrolling() = runBlockingWithManualClock(true) { clock ->
var total = 0f
val controller = ScrollableController(
@@ -426,6 +435,7 @@
}
@Test
+ @OptIn(ExperimentalTesting::class)
fun scrollable_explicitDisposal() = runBlockingWithManualClock(true) { clock ->
val disposed = mutableStateOf(false)
var total = 0f
@@ -467,6 +477,7 @@
}
@Test
+ @OptIn(ExperimentalTesting::class)
fun scrollable_nestedDrag() = runBlockingWithManualClock { clock ->
var innerDrag = 0f
var outerDrag = 0f
@@ -544,6 +555,7 @@
}
}
+ @ExperimentalTesting
private suspend fun advanceClockAndAwaitAnimation(clock: ManualFrameClock) {
waitForIdle()
withContext(TestUiDispatcher.Main) {
diff --git a/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/TextFieldTest.kt b/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/TextFieldTest.kt
index ff11221..e5c75a0 100644
--- a/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/TextFieldTest.kt
+++ b/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/TextFieldTest.kt
@@ -24,6 +24,8 @@
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.preferredSize
import androidx.compose.foundation.layout.preferredWidth
+import androidx.compose.foundation.layout.width
+import androidx.compose.foundation.text.CoreTextField
import androidx.compose.runtime.Composable
import androidx.compose.runtime.MutableState
import androidx.compose.runtime.Providers
@@ -44,8 +46,10 @@
import androidx.compose.ui.semantics.SemanticsActions
import androidx.compose.ui.semantics.SemanticsProperties
import androidx.compose.ui.text.AnnotatedString
+import androidx.compose.ui.text.InternalTextApi
import androidx.compose.ui.text.TextLayoutResult
import androidx.compose.ui.text.TextRange
+import androidx.compose.ui.text.constrain
import androidx.compose.ui.text.input.CommitTextEditOp
import androidx.compose.ui.text.input.EditOperation
import androidx.compose.ui.text.input.ImeAction
@@ -72,6 +76,8 @@
import androidx.ui.test.onNodeWithTag
import androidx.ui.test.performClick
import androidx.ui.test.performSemanticsAction
+import androidx.ui.test.performTextClearance
+import androidx.ui.test.performTextInput
import androidx.ui.test.runOnIdle
import com.google.common.truth.Truth.assertThat
import com.nhaarman.mockitokotlin2.any
@@ -98,6 +104,7 @@
val composeTestRule = createComposeRule()
private val DefaultTextFieldWidth = 280.dp
+ private val Tag = "textField"
@Test
fun textField_focusInSemantics() {
@@ -538,4 +545,62 @@
onNode(hasInputMethodsSupport())
.assert(hasImeAction(ImeAction.Search))
}
+
+ @Test
+ fun stringOverrideTextField_canDeleteLastSymbol() {
+ var lastSeenText = ""
+ composeTestRule.setContent {
+ var text by remember { mutableStateOf("") }
+ TextFieldStringOverride(
+ value = text,
+ onValueChange = {
+ text = it
+ lastSeenText = it
+ },
+ modifier = Modifier.testTag(Tag)
+ )
+ }
+
+ onNodeWithTag(Tag)
+ .performTextInput("A")
+
+ runOnIdle {
+ assertThat(lastSeenText).isEqualTo("A")
+ }
+
+ onNodeWithTag(Tag)
+ .performTextClearance(true)
+
+ runOnIdle {
+ assertThat(lastSeenText).isEqualTo("")
+ }
+ }
+}
+
+@Composable
+@OptIn(InternalTextApi::class, ExperimentalFoundationApi::class)
+private fun TextFieldStringOverride(
+ value: String,
+ onValueChange: (String) -> Unit,
+ modifier: Modifier = Modifier
+) {
+ var selection by remember { mutableStateOf(TextRange.Zero) }
+ var composition by remember { mutableStateOf<TextRange?>(null) }
+ val textFieldValue = TextFieldValue(
+ text = value,
+ selection = selection.constrain(0, value.length),
+ composition = composition?.constrain(0, value.length)
+ )
+
+ CoreTextField(
+ value = textFieldValue,
+ onValueChange = {
+ selection = it.selection
+ composition = it.composition
+ if (textFieldValue.text != it.text) {
+ onValueChange(it.text)
+ }
+ },
+ modifier = modifier.width(100.dp)
+ )
}
diff --git a/compose/foundation/foundation/src/androidMain/kotlin/androidx/compose/foundation/gestures/AndroidScrollable.kt b/compose/foundation/foundation/src/androidMain/kotlin/androidx/compose/foundation/gestures/AndroidScrollable.kt
new file mode 100644
index 0000000..3dbe196
--- /dev/null
+++ b/compose/foundation/foundation/src/androidMain/kotlin/androidx/compose/foundation/gestures/AndroidScrollable.kt
@@ -0,0 +1,40 @@
+/*
+ * Copyright 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.compose.foundation.gestures
+
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.gesture.Direction
+import androidx.compose.ui.gesture.ScrollCallback
+import androidx.compose.ui.gesture.scrollGestureFilter
+import androidx.compose.ui.gesture.scrollorientationlocking.Orientation
+
+internal actual fun Modifier.touchScrollable(
+ scrollCallback: ScrollCallback,
+ orientation: Orientation,
+ canScroll: ((Direction) -> Boolean)?,
+ startScrollImmediately: Boolean
+): Modifier = scrollGestureFilter(
+ scrollCallback,
+ orientation,
+ canScroll,
+ startScrollImmediately
+)
+
+internal actual fun Modifier.mouseScrollable(
+ scrollCallback: ScrollCallback,
+ orientation: Orientation
+): Modifier = this
\ No newline at end of file
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/InteractionState.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/InteractionState.kt
index f20a55a..6e188f5 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/InteractionState.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/InteractionState.kt
@@ -35,13 +35,31 @@
* recomposition when there are changes to the state of [Interaction], such as when a [clickable]
* becomes [Interaction.Pressed].
*
- * @sample androidx.compose.foundation.samples.InteractionStateSample
+ * For cases when you are only interested in one [Interaction], or you have a priority for cases
+ * when multiple [Interaction]s are present, you can use [contains], such as in the following
+ * example:
+ *
+ * @sample androidx.compose.foundation.samples.PriorityInteractionStateSample
+ *
+ * Often it is important to respond to the most recently added [Interaction], as this corresponds
+ * to the user's most recent interaction with a component. To enable such cases, [value] is
+ * guaranteed to have its ordering preserved, with the most recent [Interaction] added to the end.
+ * As a result, you can simply iterate / filter [value] from the end, until you find an
+ * [Interaction] you are interested in, such as in the following example:
+ *
+ * @sample androidx.compose.foundation.samples.MultipleInteractionStateSample
*/
@Stable
class InteractionState : State<Set<Interaction>> {
private var map: Map<Interaction, Offset?> by mutableStateOf(emptyMap())
+ /**
+ * The [Set] containing all [Interaction]s present in this [InteractionState]. Note that this
+ * set is ordered, and the most recently added [Interaction] will be the last element in the
+ * set. For representing the most recent [Interaction] in a component, you should iterate over
+ * the set in reversed order, until you find an [Interaction] that you are interested in.
+ */
override val value: Set<Interaction>
get() = map.keys
@@ -79,4 +97,4 @@
* @return whether the provided [interaction] exists inside this InteractionState.
*/
operator fun contains(interaction: Interaction): Boolean = map.contains(interaction)
-}
\ No newline at end of file
+}
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/gestures/Scrollable.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/gestures/Scrollable.kt
index 1123262..b634893 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/gestures/Scrollable.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/gestures/Scrollable.kt
@@ -36,7 +36,6 @@
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.gesture.Direction
import androidx.compose.ui.gesture.ScrollCallback
-import androidx.compose.ui.gesture.scrollGestureFilter
import androidx.compose.ui.gesture.scrollorientationlocking.Orientation
import androidx.compose.ui.platform.AnimationClockAmbient
@@ -179,44 +178,65 @@
controller.stopAnimation()
}
- scrollGestureFilter(
- scrollCallback = object : ScrollCallback {
+ val scrollCallback = object : ScrollCallback {
- override fun onStart(downPosition: Offset) {
- if (enabled) {
- controller.stopAnimation()
- onScrollStarted(downPosition)
- }
- }
-
- override fun onScroll(scrollDistance: Float): Float {
- if (!enabled) return 0f
+ override fun onStart(downPosition: Offset) {
+ if (enabled) {
controller.stopAnimation()
- val toConsume = if (reverseDirection) scrollDistance * -1 else scrollDistance
- val consumed = controller.consumeScrollDelta(toConsume)
- controller.value = controller.value + consumed
- return if (reverseDirection) consumed * -1 else consumed
+ onScrollStarted(downPosition)
}
+ }
- override fun onCancel() {
- if (enabled) onScrollStopped(0f)
- }
+ override fun onScroll(scrollDistance: Float): Float {
+ if (!enabled) return 0f
+ controller.stopAnimation()
+ val toConsume = if (reverseDirection) scrollDistance * -1 else scrollDistance
+ val consumed = controller.consumeScrollDelta(toConsume)
+ controller.value = controller.value + consumed
+ return if (reverseDirection) consumed * -1 else consumed
+ }
- override fun onStop(velocity: Float) {
- if (enabled) {
- controller.fling(
- velocity = if (reverseDirection) velocity * -1 else velocity,
- onScrollEnd = onScrollStopped
- )
- }
+ override fun onCancel() {
+ if (enabled) onScrollStopped(0f)
+ }
+
+ override fun onStop(velocity: Float) {
+ if (enabled) {
+ controller.fling(
+ velocity = if (reverseDirection) velocity * -1 else velocity,
+ onScrollEnd = onScrollStopped
+ )
}
- },
+ }
+ }
+
+ touchScrollable(
+ scrollCallback = scrollCallback,
orientation = orientation,
- canDrag = canScroll,
- startDragImmediately = controller.isAnimationRunning
+ canScroll = canScroll,
+ startScrollImmediately = controller.isAnimationRunning
+ ).mouseScrollable(
+ scrollCallback,
+ orientation
)
}
+internal expect fun Modifier.touchScrollable(
+ scrollCallback: ScrollCallback,
+ orientation: Orientation,
+ canScroll: ((Direction) -> Boolean)?,
+ startScrollImmediately: Boolean
+): Modifier
+
+// TODO(demin): think how we can move touchScrollable/mouseScrollable into commonMain,
+// so Android can support mouse wheel scrolling, and desktop can support touch scrolling.
+// For this we need first to implement different types of PointerInputEvent
+// (to differentiate mouse and touch)
+internal expect fun Modifier.mouseScrollable(
+ scrollCallback: ScrollCallback,
+ orientation: Orientation
+): Modifier
+
private class DeltaAnimatedFloat(
initial: Float,
clock: AnimationClockObservable,
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/LazyDsl.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/LazyDsl.kt
index e2d43cf..f9ddc142 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/LazyDsl.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/LazyDsl.kt
@@ -63,30 +63,100 @@
)
}
+private class IntervalHolder(
+ val startIndex: Int,
+ val content: LazyItemScope.(Int) -> (@Composable () -> Unit)
+)
+
private class LazyListScopeImpl : LazyListScope {
- // TODO: Avoid allocating per-item composable by saving the composable for a range of items
- val allItemsContent = mutableListOf<@Composable LazyItemScope.() -> Unit>()
+ val intervals = mutableListOf<IntervalHolder>()
+ var totalSize = 0
+
+ fun contentFor(index: Int, scope: LazyItemScope): @Composable () -> Unit {
+ val intervalIndex = findIndexOfHighestValueLesserThan(intervals, index)
+
+ val interval = intervals[intervalIndex]
+ val localIntervalIndex = index - interval.startIndex
+
+ return interval.content(scope, localIntervalIndex)
+ }
override fun <T : Any> items(
items: List<T>,
itemContent: @Composable LazyItemScope.(item: T) -> Unit
) {
- items.forEach {
- allItemsContent.add {
- itemContent(it)
+ val interval = IntervalHolder(
+ startIndex = totalSize,
+ content = { index ->
+ val item = items[index]
+
+ { itemContent(item) }
}
- }
+ )
+
+ totalSize += items.size
+
+ intervals.add(interval)
}
override fun item(content: @Composable LazyItemScope.() -> Unit) {
- allItemsContent.add(content)
+ val interval = IntervalHolder(
+ startIndex = totalSize,
+ content = { { content() } }
+ )
+
+ totalSize += 1
+
+ intervals.add(interval)
}
override fun <T : Any> itemsIndexed(
items: List<T>,
itemContent: @Composable LazyItemScope.(index: Int, item: T) -> Unit
) {
- items.forEachIndexed { index, item -> allItemsContent.add { itemContent(index, item) } }
+ val interval = IntervalHolder(
+ startIndex = totalSize,
+ content = { index ->
+ val item = items[index]
+
+ { itemContent(index, item) }
+ }
+ )
+
+ totalSize += items.size
+
+ intervals.add(interval)
+ }
+
+ /**
+ * Finds the index of the [list] which contains the highest value of [IntervalHolder.startIndex]
+ * that is less than or equal to the given [value].
+ */
+ private fun findIndexOfHighestValueLesserThan(list: List<IntervalHolder>, value: Int): Int {
+ var left = 0
+ var right = list.lastIndex
+
+ while (left < right) {
+ val middle = (left + right) / 2
+
+ val middleValue = list[middle].startIndex
+ if (middleValue == value) {
+ return middle
+ }
+
+ if (middleValue < value) {
+ left = middle + 1
+
+ // Verify that the left will not be bigger than our value
+ if (value < list[left].startIndex) {
+ return middle
+ }
+ } else {
+ right = middle - 1
+ }
+ }
+
+ return left
}
}
@@ -112,16 +182,12 @@
scope.apply(content)
LazyFor(
- itemsCount = scope.allItemsContent.size,
+ itemsCount = scope.totalSize,
modifier = modifier,
contentPadding = contentPadding,
verticalGravity = verticalGravity,
isVertical = false
- ) { index ->
- {
- scope.allItemsContent[index].invoke(this)
- }
- }
+ ) { index -> scope.contentFor(index, this) }
}
/**
@@ -146,14 +212,12 @@
scope.apply(content)
LazyFor(
- itemsCount = scope.allItemsContent.size,
+ itemsCount = scope.totalSize,
modifier = modifier,
contentPadding = contentPadding,
horizontalGravity = horizontalGravity,
isVertical = true
- ) { index ->
- {
- scope.allItemsContent[index].invoke(this)
- }
+ ) {
+ index -> scope.contentFor(index, this)
}
-}
+}
\ No newline at end of file
diff --git a/compose/foundation/foundation/src/desktopMain/kotlin/androidx/compose/foundation/gestures/DesktopScrollable.kt b/compose/foundation/foundation/src/desktopMain/kotlin/androidx/compose/foundation/gestures/DesktopScrollable.kt
new file mode 100644
index 0000000..b6a296f
--- /dev/null
+++ b/compose/foundation/foundation/src/desktopMain/kotlin/androidx/compose/foundation/gestures/DesktopScrollable.kt
@@ -0,0 +1,99 @@
+/*
+ * Copyright 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.compose.foundation.gestures
+
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.composed
+import androidx.compose.ui.gesture.Direction
+import androidx.compose.ui.gesture.ScrollCallback
+import androidx.compose.ui.gesture.scrollorientationlocking.Orientation
+import androidx.compose.ui.input.mouse.MouseScrollUnit
+import androidx.compose.ui.input.mouse.mouseScrollFilter
+import androidx.compose.ui.platform.DensityAmbient
+import androidx.compose.ui.platform.DesktopPlatform
+import androidx.compose.ui.platform.DesktopPlatformAmbient
+import androidx.compose.ui.unit.Density
+import androidx.compose.ui.unit.dp
+import kotlin.math.sqrt
+
+internal actual fun Modifier.touchScrollable(
+ scrollCallback: ScrollCallback,
+ orientation: Orientation,
+ canScroll: ((Direction) -> Boolean)?,
+ startScrollImmediately: Boolean
+): Modifier = this
+
+// TODO(demin): implement smooth scroll animation on Windows
+// TODO(demin): implement touchpad bounce physics on MacOS
+// TODO(demin): maybe we need to differentiate different linux environments (Gnome/KDE)
+// TODO(demin): do we need support real line scrolling (i.e. scroll by 3 text lines)?
+internal actual fun Modifier.mouseScrollable(
+ scrollCallback: ScrollCallback,
+ orientation: Orientation
+): Modifier = composed {
+ val density = DensityAmbient.current
+ val desktopPlatform = DesktopPlatformAmbient.current
+ val config = PlatformScrollConfig(density, desktopPlatform)
+
+ mouseScrollFilter { event, bounds ->
+ if (orientation == event.orientation) {
+ val scrollBounds = when (event.orientation) {
+ Orientation.Vertical -> bounds.height
+ Orientation.Horizontal -> bounds.width
+ }
+ scrollCallback.onScroll(-config.toScrollOffset(event.delta, scrollBounds))
+ true
+ } else {
+ false
+ }
+ }
+}
+
+private class PlatformScrollConfig(
+ private val density: Density,
+ private val desktopPlatform: DesktopPlatform
+) {
+ fun toScrollOffset(
+ unit: MouseScrollUnit,
+ bounds: Int
+ ): Float = when (unit) {
+ is MouseScrollUnit.Line -> unit.value * platformLineScrollOffset(bounds)
+
+ // TODO(demin): Chrome/Firefox on Windows scroll differently: value * 0.90f * bounds
+ // the formula was determined experimentally based on Windows Start behaviour
+ is MouseScrollUnit.Page -> unit.value * bounds.toFloat()
+ }
+
+ // TODO(demin): Chrome on Windows/Linux uses different scroll strategy
+ // (always the same scroll offset, bounds-independent).
+ // Figure out why and decide if we can use this strategy instead of current one.
+ private fun platformLineScrollOffset(bounds: Int): Float {
+ return when (desktopPlatform) {
+ // TODO(demin): is this formula actually correct? some experimental values don't fit
+ // the formula
+ // the formula was determined experimentally based on Ubuntu Nautilus behaviour
+ DesktopPlatform.Linux -> sqrt(bounds.toFloat())
+
+ // the formula was determined experimentally based on Windows Start behaviour
+ DesktopPlatform.Windows -> bounds / 20f
+
+ // the formula was determined experimentally based on MacOS Finder behaviour
+ // MacOS driver will send events with accelerating delta
+ DesktopPlatform.MacOS -> with(density) { 10.dp.toPx() }
+ }
+ }
+}
\ No newline at end of file
diff --git a/compose/foundation/foundation/src/desktopTest/kotlin/androidx/compose/foundation/gestures/DesktopScrollableTest.kt b/compose/foundation/foundation/src/desktopTest/kotlin/androidx/compose/foundation/gestures/DesktopScrollableTest.kt
new file mode 100644
index 0000000..d5384b9
--- /dev/null
+++ b/compose/foundation/foundation/src/desktopTest/kotlin/androidx/compose/foundation/gestures/DesktopScrollableTest.kt
@@ -0,0 +1,217 @@
+/*
+ * Copyright 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.compose.foundation.gestures
+
+import androidx.compose.animation.core.AnimationClockObservable
+import androidx.compose.animation.core.AnimationClockObserver
+import androidx.compose.foundation.Box
+import androidx.compose.foundation.animation.defaultFlingConfig
+import androidx.compose.foundation.layout.size
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.gesture.scrollorientationlocking.Orientation
+import androidx.compose.ui.input.mouse.MouseScrollEvent
+import androidx.compose.ui.input.mouse.MouseScrollUnit
+import androidx.compose.ui.platform.DesktopPlatform
+import androidx.compose.ui.test.TestComposeWindow
+import androidx.compose.ui.unit.Density
+import androidx.compose.ui.unit.Dp
+import androidx.compose.ui.unit.dp
+import com.google.common.truth.Truth.assertThat
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+import kotlin.math.sqrt
+
+@RunWith(JUnit4::class)
+class DesktopScrollableTest {
+ private val density = 2f
+ private val window = TestComposeWindow(width = 100, height = 100, density = Density(density))
+
+ private fun scrollLineLinux(bounds: Dp) = sqrt(bounds.value * density)
+ private fun scrollLineWindows(bounds: Dp) = bounds.value * density / 20f
+ private fun scrollLineMacOs() = density * 10f
+ private fun scrollPage(bounds: Dp) = bounds.value * density
+
+ @Test
+ fun `linux, scroll vertical`() {
+ window.desktopPlatform = DesktopPlatform.Linux
+
+ val context = TestColumn()
+
+ window.setContent {
+ Box(
+ Modifier
+ .scrollable(
+ orientation = Orientation.Vertical,
+ controller = context.controller()
+ )
+ .size(10.dp, 20.dp)
+ )
+ }
+
+ window.owners.onMouseScroll(
+ x = 0,
+ y = 0,
+ event = MouseScrollEvent(MouseScrollUnit.Line(3f), Orientation.Vertical)
+ )
+
+ assertThat(context.offset).isWithin(0.1f).of(-3 * scrollLineLinux(20.dp))
+
+ window.owners.onMouseScroll(
+ x = 0,
+ y = 0,
+ event = MouseScrollEvent(MouseScrollUnit.Line(3f), Orientation.Vertical)
+ )
+
+ assertThat(context.offset).isWithin(0.1f).of(-6 * scrollLineLinux(20.dp))
+ }
+
+ @Test
+ fun `windows, scroll vertical`() {
+ window.desktopPlatform = DesktopPlatform.Windows
+
+ val context = TestColumn()
+
+ window.setContent {
+ Box(
+ Modifier
+ .scrollable(
+ orientation = Orientation.Vertical,
+ controller = context.controller()
+ )
+ .size(10.dp, 20.dp)
+ )
+ }
+
+ window.owners.onMouseScroll(
+ x = 0,
+ y = 0,
+ event = MouseScrollEvent(MouseScrollUnit.Line(-2f), Orientation.Vertical)
+ )
+
+ assertThat(context.offset).isWithin(0.1f).of(2 * scrollLineWindows(20.dp))
+
+ window.owners.onMouseScroll(
+ x = 0,
+ y = 0,
+ event = MouseScrollEvent(MouseScrollUnit.Line(4f), Orientation.Vertical)
+ )
+
+ assertThat(context.offset).isWithin(0.1f).of(-2 * scrollLineWindows(20.dp))
+ }
+
+ @Test
+ fun `windows, scroll one page vertical`() {
+ window.desktopPlatform = DesktopPlatform.Windows
+
+ val context = TestColumn()
+
+ window.setContent {
+ Box(
+ Modifier
+ .scrollable(
+ orientation = Orientation.Vertical,
+ controller = context.controller()
+ )
+ .size(10.dp, 20.dp)
+ )
+ }
+
+ window.owners.onMouseScroll(
+ x = 0,
+ y = 0,
+ event = MouseScrollEvent(MouseScrollUnit.Page(1f), Orientation.Vertical)
+ )
+
+ assertThat(context.offset).isWithin(0.1f).of(-scrollPage(20.dp))
+ }
+
+ @Test
+ fun `macOS, scroll vertical`() {
+ window.desktopPlatform = DesktopPlatform.MacOS
+
+ val context = TestColumn()
+
+ window.setContent {
+ Box(
+ Modifier
+ .scrollable(
+ orientation = Orientation.Vertical,
+ controller = context.controller()
+ )
+ .size(10.dp, 20.dp)
+ )
+ }
+
+ window.owners.onMouseScroll(
+ x = 0,
+ y = 0,
+ event = MouseScrollEvent(MouseScrollUnit.Line(-5.5f), Orientation.Vertical)
+ )
+
+ assertThat(context.offset).isWithin(0.1f).of(5.5f * scrollLineMacOs())
+ }
+
+ @Test
+ fun `scroll with different orientation`() {
+ window.desktopPlatform = DesktopPlatform.Linux
+
+ val column = TestColumn()
+
+ window.setContent {
+ Box(
+ Modifier
+ .scrollable(
+ orientation = Orientation.Vertical,
+ controller = column.controller()
+ )
+ .size(10.dp, 20.dp)
+ )
+ }
+
+ window.owners.onMouseScroll(
+ x = 0,
+ y = 0,
+ event = MouseScrollEvent(MouseScrollUnit.Line(3f), Orientation.Horizontal)
+ )
+
+ assertThat(column.offset).isEqualTo(0f)
+ }
+
+ private class TestColumn {
+ var offset = 0f
+ private set
+
+ @Composable
+ fun controller() = ScrollableController(
+ ::consumeScrollDelta,
+ defaultFlingConfig(),
+ TestAnimationClock()
+ )
+
+ private fun consumeScrollDelta(delta: Float): Float {
+ offset += delta
+ return delta
+ }
+ }
+
+ private class TestAnimationClock : AnimationClockObservable {
+ override fun subscribe(observer: AnimationClockObserver) = Unit
+ override fun unsubscribe(observer: AnimationClockObserver) = Unit
+ }
+}
\ No newline at end of file
diff --git a/compose/foundation/foundation/src/desktopTest/kotlin/androidx/compose/ui/test/TestComposeWindow.kt b/compose/foundation/foundation/src/desktopTest/kotlin/androidx/compose/ui/test/TestComposeWindow.kt
new file mode 100644
index 0000000..bd0cd7e1
--- /dev/null
+++ b/compose/foundation/foundation/src/desktopTest/kotlin/androidx/compose/ui/test/TestComposeWindow.kt
@@ -0,0 +1,61 @@
+/*
+ * Copyright 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package androidx.compose.ui.test
+
+import androidx.compose.desktop.initCompose
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.Providers
+import androidx.compose.ui.platform.DesktopOwner
+import androidx.compose.ui.platform.DesktopOwners
+import androidx.compose.ui.platform.DesktopPlatform
+import androidx.compose.ui.platform.DesktopPlatformAmbient
+import androidx.compose.ui.platform.setContent
+import androidx.compose.ui.unit.Density
+import org.jetbrains.skija.Surface
+import java.awt.Component
+
+// TODO(demin): move to :ui:ui-test after it will have desktopMain source set
+internal class TestComposeWindow(
+ val width: Int,
+ val height: Int,
+ val density: Density = Density(1f, 1f),
+ var desktopPlatform: DesktopPlatform = DesktopPlatform.Linux
+) {
+ val surface = Surface.makeRasterN32Premul(width, height)
+ val canvas = surface.canvas
+ val component = object : Component() {}
+ val owners = DesktopOwners(component = component, redraw = {})
+
+ fun setContent(content: @Composable () -> Unit): DesktopOwners {
+ val owner = DesktopOwner(owners, density)
+ owner.setContent {
+ Providers(
+ DesktopPlatformAmbient provides desktopPlatform
+ ) {
+ content()
+ }
+ }
+ owner.setSize(width, height)
+ owner.draw(canvas)
+ return owners
+ }
+
+ companion object {
+ init {
+ initCompose()
+ }
+ }
+}
\ No newline at end of file
diff --git a/compose/foundation/foundation/src/test/kotlin/androidx/compose/foundation/InteractionStateTest.kt b/compose/foundation/foundation/src/test/kotlin/androidx/compose/foundation/InteractionStateTest.kt
new file mode 100644
index 0000000..0c6969a
--- /dev/null
+++ b/compose/foundation/foundation/src/test/kotlin/androidx/compose/foundation/InteractionStateTest.kt
@@ -0,0 +1,60 @@
+/*
+ * Copyright 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.compose.foundation
+
+import com.google.common.truth.Truth.assertThat
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+
+@RunWith(JUnit4::class)
+class InteractionStateTest {
+
+ private object TestInteraction1 : Interaction
+ private object TestInteraction2 : Interaction
+ private object TestInteraction3 : Interaction
+
+ @Test
+ fun orderingIsPreserved() {
+ val state = InteractionState()
+
+ state.addInteraction(TestInteraction1)
+ state.addInteraction(TestInteraction2)
+
+ assertThat(state.value)
+ .containsExactlyElementsIn(
+ listOf(TestInteraction1, TestInteraction2)
+ )
+ .inOrder()
+
+ state.addInteraction(TestInteraction3)
+
+ assertThat(state.value)
+ .containsExactlyElementsIn(
+ listOf(TestInteraction1, TestInteraction2, TestInteraction3)
+ )
+ .inOrder()
+
+ state.removeInteraction(TestInteraction2)
+
+ assertThat(state.value)
+ .containsExactlyElementsIn(
+ listOf(TestInteraction1, TestInteraction3)
+ )
+ .inOrder()
+ }
+}
diff --git a/compose/integration-tests/src/androidTest/java/androidx/ui/integration/test/VectorAssetTest.kt b/compose/integration-tests/src/androidTest/java/androidx/ui/integration/test/VectorAssetTest.kt
index 4393ccb..b72c88f 100644
--- a/compose/integration-tests/src/androidTest/java/androidx/ui/integration/test/VectorAssetTest.kt
+++ b/compose/integration-tests/src/androidTest/java/androidx/ui/integration/test/VectorAssetTest.kt
@@ -17,9 +17,16 @@
package androidx.ui.integration.test
import android.os.Build
+import androidx.compose.foundation.Image
import androidx.test.filters.LargeTest
import androidx.test.filters.SdkSuppress
import androidx.compose.foundation.layout.Column
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.graphics.toArgb
+import androidx.compose.ui.platform.DensityAmbient
+import androidx.compose.ui.platform.testTag
+import androidx.compose.ui.res.vectorResource
import androidx.ui.test.captureToBitmap
import androidx.ui.integration.test.framework.ProgrammaticVectorTestCase
import androidx.ui.integration.test.framework.XmlVectorTestCase
@@ -31,6 +38,7 @@
import org.junit.Test
import org.junit.runner.RunWith
import org.junit.runners.JUnit4
+import kotlin.math.roundToInt
/**
* Test to ensure that [XmlVectorTestCase] and [ProgrammaticVectorTestCase] have an identical pixel
@@ -75,4 +83,35 @@
assertArrayEquals(xmlPixelArray, programmaticBitmapArray)
}
+
+ @Test
+ fun testEvenOddPathType() {
+ val testTag = "testTag"
+ var insetRectSize: Int = 0
+ composeTestRule.setContent {
+ with(DensityAmbient.current) {
+ insetRectSize = (10f * this.density).roundToInt()
+ }
+ val vectorAsset =
+ vectorResource(R.drawable.ic_pathfill_sample)
+ Image(vectorAsset, modifier = Modifier.testTag(testTag))
+ }
+
+ onNodeWithTag(testTag).captureToBitmap().apply {
+ assertEquals(Color.Blue.toArgb(), getPixel(0, 0))
+ assertEquals(Color.Blue.toArgb(), getPixel(width - 1, 0))
+ assertEquals(Color.Blue.toArgb(), getPixel(0, height - 1))
+ assertEquals(Color.Blue.toArgb(), getPixel(width - 1, height - 1))
+
+ assertEquals(Color.Blue.toArgb(), getPixel(width / 2, height / 2))
+
+ assertEquals(Color.Red.toArgb(), getPixel(insetRectSize + 2, insetRectSize + 2))
+ assertEquals(Color.Red.toArgb(), getPixel(width - insetRectSize - 2,
+ insetRectSize + 2))
+ assertEquals(Color.Red.toArgb(), getPixel(insetRectSize + 2,
+ height - insetRectSize - 2))
+ assertEquals(Color.Red.toArgb(), getPixel(width - insetRectSize - 2, height -
+ insetRectSize - 2))
+ }
+ }
}
diff --git a/compose/integration-tests/src/main/res/drawable/ic_pathfill_sample.xml b/compose/integration-tests/src/main/res/drawable/ic_pathfill_sample.xml
new file mode 100644
index 0000000..350c704
--- /dev/null
+++ b/compose/integration-tests/src/main/res/drawable/ic_pathfill_sample.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright 2020 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ -->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:height="50dp"
+ android:width="50dp"
+ android:viewportHeight="50"
+ android:viewportWidth="50" >
+
+ <path
+ android:fillColor="#0000FF"
+ android:pathData="L 50 0 50 50 0 50" />
+
+ <path
+ android:fillColor="#FF0000"
+ android:fillType="evenOdd"
+ android:pathData="M 10 10 L 40 10 40 40 10 40 z M 20 20 L 30 20 30 30 20 30 z" />
+</vector>
diff --git a/compose/material/material-icons-core/api/current.txt b/compose/material/material-icons-core/api/current.txt
index 9369529..cb0e7ca 100644
--- a/compose/material/material-icons-core/api/current.txt
+++ b/compose/material/material-icons-core/api/current.txt
@@ -29,7 +29,7 @@
public final class IconsKt {
method public static inline androidx.compose.ui.graphics.vector.VectorAsset materialIcon(kotlin.jvm.functions.Function1<? super androidx.compose.ui.graphics.vector.VectorAssetBuilder,androidx.compose.ui.graphics.vector.VectorAssetBuilder> block);
- method public static inline androidx.compose.ui.graphics.vector.VectorAssetBuilder materialPath(androidx.compose.ui.graphics.vector.VectorAssetBuilder, float fillAlpha = 1f, float strokeAlpha = 1f, kotlin.jvm.functions.Function1<? super androidx.compose.ui.graphics.vector.PathBuilder,kotlin.Unit> pathBuilder);
+ method public static inline androidx.compose.ui.graphics.vector.VectorAssetBuilder materialPath(androidx.compose.ui.graphics.vector.VectorAssetBuilder, float fillAlpha = 1f, float strokeAlpha = 1f, androidx.compose.ui.graphics.PathFillType pathFillType = androidx.compose.ui.graphics.vector.VectorKt.DefaultFillType, kotlin.jvm.functions.Function1<? super androidx.compose.ui.graphics.vector.PathBuilder,kotlin.Unit> pathBuilder);
}
}
diff --git a/compose/material/material-icons-core/api/public_plus_experimental_current.txt b/compose/material/material-icons-core/api/public_plus_experimental_current.txt
index 9369529..cb0e7ca 100644
--- a/compose/material/material-icons-core/api/public_plus_experimental_current.txt
+++ b/compose/material/material-icons-core/api/public_plus_experimental_current.txt
@@ -29,7 +29,7 @@
public final class IconsKt {
method public static inline androidx.compose.ui.graphics.vector.VectorAsset materialIcon(kotlin.jvm.functions.Function1<? super androidx.compose.ui.graphics.vector.VectorAssetBuilder,androidx.compose.ui.graphics.vector.VectorAssetBuilder> block);
- method public static inline androidx.compose.ui.graphics.vector.VectorAssetBuilder materialPath(androidx.compose.ui.graphics.vector.VectorAssetBuilder, float fillAlpha = 1f, float strokeAlpha = 1f, kotlin.jvm.functions.Function1<? super androidx.compose.ui.graphics.vector.PathBuilder,kotlin.Unit> pathBuilder);
+ method public static inline androidx.compose.ui.graphics.vector.VectorAssetBuilder materialPath(androidx.compose.ui.graphics.vector.VectorAssetBuilder, float fillAlpha = 1f, float strokeAlpha = 1f, androidx.compose.ui.graphics.PathFillType pathFillType = androidx.compose.ui.graphics.vector.VectorKt.DefaultFillType, kotlin.jvm.functions.Function1<? super androidx.compose.ui.graphics.vector.PathBuilder,kotlin.Unit> pathBuilder);
}
}
diff --git a/compose/material/material-icons-core/api/restricted_current.txt b/compose/material/material-icons-core/api/restricted_current.txt
index 10dfb73..a202864 100644
--- a/compose/material/material-icons-core/api/restricted_current.txt
+++ b/compose/material/material-icons-core/api/restricted_current.txt
@@ -29,7 +29,7 @@
public final class IconsKt {
method public static inline androidx.compose.ui.graphics.vector.VectorAsset materialIcon(kotlin.jvm.functions.Function1<? super androidx.compose.ui.graphics.vector.VectorAssetBuilder,androidx.compose.ui.graphics.vector.VectorAssetBuilder> block);
- method public static inline androidx.compose.ui.graphics.vector.VectorAssetBuilder materialPath(androidx.compose.ui.graphics.vector.VectorAssetBuilder, float fillAlpha = 1f, float strokeAlpha = 1f, kotlin.jvm.functions.Function1<? super androidx.compose.ui.graphics.vector.PathBuilder,kotlin.Unit> pathBuilder);
+ method public static inline androidx.compose.ui.graphics.vector.VectorAssetBuilder materialPath(androidx.compose.ui.graphics.vector.VectorAssetBuilder, float fillAlpha = 1f, float strokeAlpha = 1f, androidx.compose.ui.graphics.PathFillType pathFillType = androidx.compose.ui.graphics.vector.VectorKt.DefaultFillType, kotlin.jvm.functions.Function1<? super androidx.compose.ui.graphics.vector.PathBuilder,kotlin.Unit> pathBuilder);
field @kotlin.PublishedApi internal static final float MaterialIconDimension = 24.0f;
}
diff --git a/compose/material/material-icons-core/build.gradle b/compose/material/material-icons-core/build.gradle
index e1f499b..c752ff3 100644
--- a/compose/material/material-icons-core/build.gradle
+++ b/compose/material/material-icons-core/build.gradle
@@ -50,7 +50,7 @@
androidx {
name = "Compose Material Icons Core"
publish = Publish.SNAPSHOT_AND_RELEASE
- mavenVersion = LibraryVersions.UI
+ mavenVersion = LibraryVersions.COMPOSE
mavenGroup = LibraryGroups.Compose.MATERIAL
inceptionYear = "2020"
description = "Compose Material Design core icons. This module contains the most commonly used set of Material icons."
diff --git a/compose/material/material-icons-core/src/commonMain/kotlin/androidx/compose/material/icons/Icons.kt b/compose/material/material-icons-core/src/commonMain/kotlin/androidx/compose/material/icons/Icons.kt
index 5e3bc08..cef9c83 100644
--- a/compose/material/material-icons-core/src/commonMain/kotlin/androidx/compose/material/icons/Icons.kt
+++ b/compose/material/material-icons-core/src/commonMain/kotlin/androidx/compose/material/icons/Icons.kt
@@ -22,9 +22,11 @@
import androidx.compose.material.icons.Icons.Sharp
import androidx.compose.material.icons.Icons.TwoTone
import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.graphics.PathFillType
import androidx.compose.ui.graphics.SolidColor
import androidx.compose.ui.graphics.StrokeCap
import androidx.compose.ui.graphics.StrokeJoin
+import androidx.compose.ui.graphics.vector.DefaultFillType
import androidx.compose.ui.graphics.vector.PathBuilder
import androidx.compose.ui.graphics.vector.VectorAsset
import androidx.compose.ui.graphics.vector.VectorAssetBuilder
@@ -113,11 +115,13 @@
*
* @param fillAlpha fill alpha for this path
* @param strokeAlpha stroke alpha for this path
+ * @param pathFillType [PathFillType] for this path
* @param pathBuilder builder lambda to add commands to this path
*/
inline fun VectorAssetBuilder.materialPath(
fillAlpha: Float = 1f,
strokeAlpha: Float = 1f,
+ pathFillType: PathFillType = DefaultFillType,
pathBuilder: PathBuilder.() -> Unit
) =
// TODO: b/146213225
@@ -132,6 +136,7 @@
strokeLineCap = StrokeCap.Butt,
strokeLineJoin = StrokeJoin.Bevel,
strokeLineMiter = 1f,
+ pathFillType = pathFillType,
pathBuilder = pathBuilder
)
diff --git a/compose/material/material-icons-extended/build.gradle b/compose/material/material-icons-extended/build.gradle
index be0bbe18..2ac2813 100644
--- a/compose/material/material-icons-extended/build.gradle
+++ b/compose/material/material-icons-extended/build.gradle
@@ -67,7 +67,7 @@
androidx {
name = "Compose Material Icons Extended"
publish = Publish.SNAPSHOT_AND_RELEASE
- mavenVersion = LibraryVersions.UI
+ mavenVersion = LibraryVersions.COMPOSE
mavenGroup = LibraryGroups.Compose.MATERIAL
// This module has a large number (5000+) of generated source files and so doc generation /
// API tracking will simply take too long
diff --git a/compose/material/material/api/current.txt b/compose/material/material/api/current.txt
index 009630d..387e97b 100644
--- a/compose/material/material/api/current.txt
+++ b/compose/material/material/api/current.txt
@@ -12,6 +12,48 @@
method @androidx.compose.runtime.Composable public static void TopAppBar-ye6PvEY(androidx.compose.ui.Modifier modifier = Modifier, long backgroundColor = MaterialTheme.colors.primarySurface, long contentColor = contentColorFor(backgroundColor), float elevation = androidx.compose.material.AppBarKt.TopAppBarElevation, kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.RowScope,kotlin.Unit> content);
}
+ public final class BackdropConstants {
+ method public float getDefaultFrontLayerElevation();
+ method public long getDefaultFrontLayerScrimColor();
+ method public androidx.compose.ui.graphics.Shape getDefaultFrontLayerShape();
+ method public float getDefaultHeaderHeight();
+ method public float getDefaultPeekHeight();
+ property public final float DefaultFrontLayerElevation;
+ property public final long DefaultFrontLayerScrimColor;
+ property public final androidx.compose.ui.graphics.Shape DefaultFrontLayerShape;
+ property public final float DefaultHeaderHeight;
+ property public final float DefaultPeekHeight;
+ field public static final androidx.compose.material.BackdropConstants INSTANCE;
+ }
+
+ public final class BackdropScaffoldKt {
+ method @androidx.compose.material.ExperimentalMaterialApi @androidx.compose.runtime.Composable public static void BackdropScaffold-n7o2bDw(androidx.compose.ui.Modifier modifier = Modifier, androidx.compose.material.BackdropScaffoldState backdropScaffoldState = rememberBackdropState(Concealed), boolean gesturesEnabled = true, float peekHeight = BackdropConstants.DefaultPeekHeight, float headerHeight = BackdropConstants.DefaultHeaderHeight, boolean persistentAppBar = true, boolean stickyFrontLayer = true, long backLayerBackgroundColor = MaterialTheme.colors.primary, long backLayerContentColor = contentColorFor(backLayerBackgroundColor), androidx.compose.ui.graphics.Shape frontLayerShape = BackdropConstants.DefaultFrontLayerShape, float frontLayerElevation = BackdropConstants.DefaultFrontLayerElevation, long frontLayerBackgroundColor = MaterialTheme.colors.surface, long frontLayerContentColor = contentColorFor(frontLayerBackgroundColor), long frontLayerScrimColor = BackdropConstants.DefaultFrontLayerScrimColor, kotlin.jvm.functions.Function1<? super androidx.compose.material.SnackbarHostState,kotlin.Unit> snackbarHost = { SnackbarHost(it) }, kotlin.jvm.functions.Function0<kotlin.Unit> appBar, kotlin.jvm.functions.Function0<kotlin.Unit> backLayerContent, kotlin.jvm.functions.Function0<kotlin.Unit> frontLayerContent);
+ method @androidx.compose.material.ExperimentalMaterialApi @androidx.compose.runtime.Composable public static androidx.compose.material.BackdropScaffoldState rememberBackdropState(androidx.compose.material.BackdropValue initialValue, androidx.compose.animation.core.AnimationSpec<java.lang.Float> animationSpec = SwipeableConstants.DefaultAnimationSpec, kotlin.jvm.functions.Function1<? super androidx.compose.material.BackdropValue,java.lang.Boolean> confirmStateChange = { return true }, androidx.compose.material.SnackbarHostState snackbarHostState = androidx.compose.material.SnackbarHostState());
+ }
+
+ @androidx.compose.material.ExperimentalMaterialApi public final class BackdropScaffoldState extends androidx.compose.material.SwipeableState<androidx.compose.material.BackdropValue> {
+ ctor public BackdropScaffoldState(androidx.compose.material.BackdropValue initialValue, androidx.compose.animation.core.AnimationClockObservable clock, androidx.compose.animation.core.AnimationSpec<java.lang.Float> animationSpec, kotlin.jvm.functions.Function1<? super androidx.compose.material.BackdropValue,java.lang.Boolean> confirmStateChange, androidx.compose.material.SnackbarHostState snackbarHostState);
+ method public void conceal(kotlin.jvm.functions.Function0<kotlin.Unit>? onConcealed = null);
+ method public androidx.compose.material.SnackbarHostState getSnackbarHostState();
+ method public boolean isConcealed();
+ method public boolean isRevealed();
+ method public void reveal(kotlin.jvm.functions.Function0<kotlin.Unit>? onRevealed = null);
+ property public final boolean isConcealed;
+ property public final boolean isRevealed;
+ field public static final androidx.compose.material.BackdropScaffoldState.Companion Companion;
+ }
+
+ public static final class BackdropScaffoldState.Companion {
+ method public androidx.compose.runtime.savedinstancestate.Saver<androidx.compose.material.BackdropScaffoldState,?> Saver(androidx.compose.animation.core.AnimationClockObservable clock, androidx.compose.animation.core.AnimationSpec<java.lang.Float> animationSpec, kotlin.jvm.functions.Function1<? super androidx.compose.material.BackdropValue,java.lang.Boolean> confirmStateChange, androidx.compose.material.SnackbarHostState snackbarHostState);
+ }
+
+ public enum BackdropValue {
+ method public static androidx.compose.material.BackdropValue valueOf(String name) throws java.lang.IllegalArgumentException;
+ method public static androidx.compose.material.BackdropValue[] values();
+ enum_constant public static final androidx.compose.material.BackdropValue Concealed;
+ enum_constant public static final androidx.compose.material.BackdropValue Revealed;
+ }
+
public final class BottomDrawerState extends androidx.compose.material.SwipeableState<androidx.compose.material.BottomDrawerValue> {
ctor public BottomDrawerState(androidx.compose.material.BottomDrawerValue initialValue, androidx.compose.animation.core.AnimationClockObservable clock, kotlin.jvm.functions.Function1<? super androidx.compose.material.BottomDrawerValue,java.lang.Boolean> confirmStateChange);
method public void close(kotlin.jvm.functions.Function0<kotlin.Unit>? onClosed = null);
@@ -262,8 +304,8 @@
}
public final class OutlinedTextFieldKt {
- method @androidx.compose.runtime.Composable public static void OutlinedTextField-Lv3QS5c(androidx.compose.ui.text.input.TextFieldValue value, kotlin.jvm.functions.Function1<? super androidx.compose.ui.text.input.TextFieldValue,kotlin.Unit> onValueChange, kotlin.jvm.functions.Function0<kotlin.Unit> label, androidx.compose.ui.Modifier modifier = Modifier, androidx.compose.ui.text.TextStyle textStyle = currentTextStyle(), kotlin.jvm.functions.Function0<kotlin.Unit>? placeholder = null, kotlin.jvm.functions.Function0<kotlin.Unit>? leadingIcon = null, kotlin.jvm.functions.Function0<kotlin.Unit>? trailingIcon = null, boolean isErrorValue = false, androidx.compose.ui.text.input.VisualTransformation visualTransformation = VisualTransformation.None, androidx.compose.ui.text.input.KeyboardType keyboardType = KeyboardType.Text, androidx.compose.ui.text.input.ImeAction imeAction = ImeAction.Unspecified, kotlin.jvm.functions.Function2<? super androidx.compose.ui.text.input.ImeAction,? super androidx.compose.ui.text.SoftwareKeyboardController,kotlin.Unit> onImeActionPerformed = { _, _ -> }, kotlin.jvm.functions.Function1<? super androidx.compose.ui.text.SoftwareKeyboardController,kotlin.Unit> onTextInputStarted = {}, long activeColor = MaterialTheme.colors.primary, long inactiveColor = MaterialTheme.colors.onSurface, long errorColor = MaterialTheme.colors.error);
- method @androidx.compose.runtime.Composable public static void OutlinedTextField-iUY4m8E(String value, kotlin.jvm.functions.Function1<? super java.lang.String,kotlin.Unit> onValueChange, kotlin.jvm.functions.Function0<kotlin.Unit> label, androidx.compose.ui.Modifier modifier = Modifier, androidx.compose.ui.text.TextStyle textStyle = currentTextStyle(), kotlin.jvm.functions.Function0<kotlin.Unit>? placeholder = null, kotlin.jvm.functions.Function0<kotlin.Unit>? leadingIcon = null, kotlin.jvm.functions.Function0<kotlin.Unit>? trailingIcon = null, boolean isErrorValue = false, androidx.compose.ui.text.input.VisualTransformation visualTransformation = VisualTransformation.None, androidx.compose.ui.text.input.KeyboardType keyboardType = KeyboardType.Text, androidx.compose.ui.text.input.ImeAction imeAction = ImeAction.Unspecified, kotlin.jvm.functions.Function2<? super androidx.compose.ui.text.input.ImeAction,? super androidx.compose.ui.text.SoftwareKeyboardController,kotlin.Unit> onImeActionPerformed = { _, _ -> }, kotlin.jvm.functions.Function1<? super androidx.compose.ui.text.SoftwareKeyboardController,kotlin.Unit> onTextInputStarted = {}, long activeColor = MaterialTheme.colors.primary, long inactiveColor = MaterialTheme.colors.onSurface, long errorColor = MaterialTheme.colors.error);
+ method @androidx.compose.runtime.Composable public static void OutlinedTextField--KhY4tc(androidx.compose.ui.text.input.TextFieldValue value, kotlin.jvm.functions.Function1<? super androidx.compose.ui.text.input.TextFieldValue,kotlin.Unit> onValueChange, androidx.compose.ui.Modifier modifier = Modifier, androidx.compose.ui.text.TextStyle textStyle = currentTextStyle(), kotlin.jvm.functions.Function0<kotlin.Unit>? label = null, kotlin.jvm.functions.Function0<kotlin.Unit>? placeholder = null, kotlin.jvm.functions.Function0<kotlin.Unit>? leadingIcon = null, kotlin.jvm.functions.Function0<kotlin.Unit>? trailingIcon = null, boolean isErrorValue = false, androidx.compose.ui.text.input.VisualTransformation visualTransformation = VisualTransformation.None, androidx.compose.ui.text.input.KeyboardType keyboardType = KeyboardType.Text, androidx.compose.ui.text.input.ImeAction imeAction = ImeAction.Unspecified, kotlin.jvm.functions.Function2<? super androidx.compose.ui.text.input.ImeAction,? super androidx.compose.ui.text.SoftwareKeyboardController,kotlin.Unit> onImeActionPerformed = { _, _ -> }, kotlin.jvm.functions.Function1<? super androidx.compose.ui.text.SoftwareKeyboardController,kotlin.Unit> onTextInputStarted = {}, long activeColor = MaterialTheme.colors.primary, long inactiveColor = MaterialTheme.colors.onSurface, long errorColor = MaterialTheme.colors.error);
+ method @androidx.compose.runtime.Composable public static void OutlinedTextField-YV8zA4E(String value, kotlin.jvm.functions.Function1<? super java.lang.String,kotlin.Unit> onValueChange, androidx.compose.ui.Modifier modifier = Modifier, androidx.compose.ui.text.TextStyle textStyle = currentTextStyle(), kotlin.jvm.functions.Function0<kotlin.Unit>? label = null, kotlin.jvm.functions.Function0<kotlin.Unit>? placeholder = null, kotlin.jvm.functions.Function0<kotlin.Unit>? leadingIcon = null, kotlin.jvm.functions.Function0<kotlin.Unit>? trailingIcon = null, boolean isErrorValue = false, androidx.compose.ui.text.input.VisualTransformation visualTransformation = VisualTransformation.None, androidx.compose.ui.text.input.KeyboardType keyboardType = KeyboardType.Text, androidx.compose.ui.text.input.ImeAction imeAction = ImeAction.Unspecified, kotlin.jvm.functions.Function2<? super androidx.compose.ui.text.input.ImeAction,? super androidx.compose.ui.text.SoftwareKeyboardController,kotlin.Unit> onImeActionPerformed = { _, _ -> }, kotlin.jvm.functions.Function1<? super androidx.compose.ui.text.SoftwareKeyboardController,kotlin.Unit> onTextInputStarted = {}, long activeColor = MaterialTheme.colors.primary, long inactiveColor = MaterialTheme.colors.onSurface, long errorColor = MaterialTheme.colors.error);
}
public final class ProgressIndicatorConstants {
@@ -486,8 +528,8 @@
}
public final class TextFieldKt {
- method @androidx.compose.runtime.Composable public static void TextField-CfoTMnE(String value, kotlin.jvm.functions.Function1<? super java.lang.String,kotlin.Unit> onValueChange, kotlin.jvm.functions.Function0<kotlin.Unit> label, androidx.compose.ui.Modifier modifier = Modifier, androidx.compose.ui.text.TextStyle textStyle = currentTextStyle(), kotlin.jvm.functions.Function0<kotlin.Unit>? placeholder = null, kotlin.jvm.functions.Function0<kotlin.Unit>? leadingIcon = null, kotlin.jvm.functions.Function0<kotlin.Unit>? trailingIcon = null, boolean isErrorValue = false, androidx.compose.ui.text.input.VisualTransformation visualTransformation = VisualTransformation.None, androidx.compose.ui.text.input.KeyboardType keyboardType = KeyboardType.Text, androidx.compose.ui.text.input.ImeAction imeAction = ImeAction.Unspecified, kotlin.jvm.functions.Function2<? super androidx.compose.ui.text.input.ImeAction,? super androidx.compose.ui.text.SoftwareKeyboardController,kotlin.Unit> onImeActionPerformed = { _, _ -> }, kotlin.jvm.functions.Function1<? super androidx.compose.ui.text.SoftwareKeyboardController,kotlin.Unit> onTextInputStarted = {}, long activeColor = MaterialTheme.colors.primary, long inactiveColor = MaterialTheme.colors.onSurface, long errorColor = MaterialTheme.colors.error, long backgroundColor = MaterialTheme.colors.onSurface, androidx.compose.ui.graphics.Shape shape = MaterialTheme.shapes.small.copy(ZeroCornerSize, ZeroCornerSize));
- method @androidx.compose.runtime.Composable public static void TextField-ToDxGl4(androidx.compose.ui.text.input.TextFieldValue value, kotlin.jvm.functions.Function1<? super androidx.compose.ui.text.input.TextFieldValue,kotlin.Unit> onValueChange, kotlin.jvm.functions.Function0<kotlin.Unit> label, androidx.compose.ui.Modifier modifier = Modifier, androidx.compose.ui.text.TextStyle textStyle = currentTextStyle(), kotlin.jvm.functions.Function0<kotlin.Unit>? placeholder = null, kotlin.jvm.functions.Function0<kotlin.Unit>? leadingIcon = null, kotlin.jvm.functions.Function0<kotlin.Unit>? trailingIcon = null, boolean isErrorValue = false, androidx.compose.ui.text.input.VisualTransformation visualTransformation = VisualTransformation.None, androidx.compose.ui.text.input.KeyboardType keyboardType = KeyboardType.Text, androidx.compose.ui.text.input.ImeAction imeAction = ImeAction.Unspecified, kotlin.jvm.functions.Function2<? super androidx.compose.ui.text.input.ImeAction,? super androidx.compose.ui.text.SoftwareKeyboardController,kotlin.Unit> onImeActionPerformed = { _, _ -> }, kotlin.jvm.functions.Function1<? super androidx.compose.ui.text.SoftwareKeyboardController,kotlin.Unit> onTextInputStarted = {}, long activeColor = MaterialTheme.colors.primary, long inactiveColor = MaterialTheme.colors.onSurface, long errorColor = MaterialTheme.colors.error, long backgroundColor = MaterialTheme.colors.onSurface, androidx.compose.ui.graphics.Shape shape = MaterialTheme.shapes.small.copy(ZeroCornerSize, ZeroCornerSize));
+ method @androidx.compose.runtime.Composable public static void TextField-enE39lU(String value, kotlin.jvm.functions.Function1<? super java.lang.String,kotlin.Unit> onValueChange, androidx.compose.ui.Modifier modifier = Modifier, androidx.compose.ui.text.TextStyle textStyle = currentTextStyle(), kotlin.jvm.functions.Function0<kotlin.Unit>? label = null, kotlin.jvm.functions.Function0<kotlin.Unit>? placeholder = null, kotlin.jvm.functions.Function0<kotlin.Unit>? leadingIcon = null, kotlin.jvm.functions.Function0<kotlin.Unit>? trailingIcon = null, boolean isErrorValue = false, androidx.compose.ui.text.input.VisualTransformation visualTransformation = VisualTransformation.None, androidx.compose.ui.text.input.KeyboardType keyboardType = KeyboardType.Text, androidx.compose.ui.text.input.ImeAction imeAction = ImeAction.Unspecified, kotlin.jvm.functions.Function2<? super androidx.compose.ui.text.input.ImeAction,? super androidx.compose.ui.text.SoftwareKeyboardController,kotlin.Unit> onImeActionPerformed = { _, _ -> }, kotlin.jvm.functions.Function1<? super androidx.compose.ui.text.SoftwareKeyboardController,kotlin.Unit> onTextInputStarted = {}, long activeColor = MaterialTheme.colors.primary, long inactiveColor = MaterialTheme.colors.onSurface, long errorColor = MaterialTheme.colors.error, long backgroundColor = MaterialTheme.colors.onSurface, androidx.compose.ui.graphics.Shape shape = MaterialTheme.shapes.small.copy(ZeroCornerSize, ZeroCornerSize));
+ method @androidx.compose.runtime.Composable public static void TextField-tHV9mug(androidx.compose.ui.text.input.TextFieldValue value, kotlin.jvm.functions.Function1<? super androidx.compose.ui.text.input.TextFieldValue,kotlin.Unit> onValueChange, androidx.compose.ui.Modifier modifier = Modifier, androidx.compose.ui.text.TextStyle textStyle = currentTextStyle(), kotlin.jvm.functions.Function0<kotlin.Unit>? label = null, kotlin.jvm.functions.Function0<kotlin.Unit>? placeholder = null, kotlin.jvm.functions.Function0<kotlin.Unit>? leadingIcon = null, kotlin.jvm.functions.Function0<kotlin.Unit>? trailingIcon = null, boolean isErrorValue = false, androidx.compose.ui.text.input.VisualTransformation visualTransformation = VisualTransformation.None, androidx.compose.ui.text.input.KeyboardType keyboardType = KeyboardType.Text, androidx.compose.ui.text.input.ImeAction imeAction = ImeAction.Unspecified, kotlin.jvm.functions.Function2<? super androidx.compose.ui.text.input.ImeAction,? super androidx.compose.ui.text.SoftwareKeyboardController,kotlin.Unit> onImeActionPerformed = { _, _ -> }, kotlin.jvm.functions.Function1<? super androidx.compose.ui.text.SoftwareKeyboardController,kotlin.Unit> onTextInputStarted = {}, long activeColor = MaterialTheme.colors.primary, long inactiveColor = MaterialTheme.colors.onSurface, long errorColor = MaterialTheme.colors.error, long backgroundColor = MaterialTheme.colors.onSurface, androidx.compose.ui.graphics.Shape shape = MaterialTheme.shapes.small.copy(ZeroCornerSize, ZeroCornerSize));
}
@androidx.compose.material.ExperimentalMaterialApi @androidx.compose.runtime.Stable public interface ThresholdConfig {
diff --git a/compose/material/material/api/public_plus_experimental_current.txt b/compose/material/material/api/public_plus_experimental_current.txt
index 009630d..387e97b 100644
--- a/compose/material/material/api/public_plus_experimental_current.txt
+++ b/compose/material/material/api/public_plus_experimental_current.txt
@@ -12,6 +12,48 @@
method @androidx.compose.runtime.Composable public static void TopAppBar-ye6PvEY(androidx.compose.ui.Modifier modifier = Modifier, long backgroundColor = MaterialTheme.colors.primarySurface, long contentColor = contentColorFor(backgroundColor), float elevation = androidx.compose.material.AppBarKt.TopAppBarElevation, kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.RowScope,kotlin.Unit> content);
}
+ public final class BackdropConstants {
+ method public float getDefaultFrontLayerElevation();
+ method public long getDefaultFrontLayerScrimColor();
+ method public androidx.compose.ui.graphics.Shape getDefaultFrontLayerShape();
+ method public float getDefaultHeaderHeight();
+ method public float getDefaultPeekHeight();
+ property public final float DefaultFrontLayerElevation;
+ property public final long DefaultFrontLayerScrimColor;
+ property public final androidx.compose.ui.graphics.Shape DefaultFrontLayerShape;
+ property public final float DefaultHeaderHeight;
+ property public final float DefaultPeekHeight;
+ field public static final androidx.compose.material.BackdropConstants INSTANCE;
+ }
+
+ public final class BackdropScaffoldKt {
+ method @androidx.compose.material.ExperimentalMaterialApi @androidx.compose.runtime.Composable public static void BackdropScaffold-n7o2bDw(androidx.compose.ui.Modifier modifier = Modifier, androidx.compose.material.BackdropScaffoldState backdropScaffoldState = rememberBackdropState(Concealed), boolean gesturesEnabled = true, float peekHeight = BackdropConstants.DefaultPeekHeight, float headerHeight = BackdropConstants.DefaultHeaderHeight, boolean persistentAppBar = true, boolean stickyFrontLayer = true, long backLayerBackgroundColor = MaterialTheme.colors.primary, long backLayerContentColor = contentColorFor(backLayerBackgroundColor), androidx.compose.ui.graphics.Shape frontLayerShape = BackdropConstants.DefaultFrontLayerShape, float frontLayerElevation = BackdropConstants.DefaultFrontLayerElevation, long frontLayerBackgroundColor = MaterialTheme.colors.surface, long frontLayerContentColor = contentColorFor(frontLayerBackgroundColor), long frontLayerScrimColor = BackdropConstants.DefaultFrontLayerScrimColor, kotlin.jvm.functions.Function1<? super androidx.compose.material.SnackbarHostState,kotlin.Unit> snackbarHost = { SnackbarHost(it) }, kotlin.jvm.functions.Function0<kotlin.Unit> appBar, kotlin.jvm.functions.Function0<kotlin.Unit> backLayerContent, kotlin.jvm.functions.Function0<kotlin.Unit> frontLayerContent);
+ method @androidx.compose.material.ExperimentalMaterialApi @androidx.compose.runtime.Composable public static androidx.compose.material.BackdropScaffoldState rememberBackdropState(androidx.compose.material.BackdropValue initialValue, androidx.compose.animation.core.AnimationSpec<java.lang.Float> animationSpec = SwipeableConstants.DefaultAnimationSpec, kotlin.jvm.functions.Function1<? super androidx.compose.material.BackdropValue,java.lang.Boolean> confirmStateChange = { return true }, androidx.compose.material.SnackbarHostState snackbarHostState = androidx.compose.material.SnackbarHostState());
+ }
+
+ @androidx.compose.material.ExperimentalMaterialApi public final class BackdropScaffoldState extends androidx.compose.material.SwipeableState<androidx.compose.material.BackdropValue> {
+ ctor public BackdropScaffoldState(androidx.compose.material.BackdropValue initialValue, androidx.compose.animation.core.AnimationClockObservable clock, androidx.compose.animation.core.AnimationSpec<java.lang.Float> animationSpec, kotlin.jvm.functions.Function1<? super androidx.compose.material.BackdropValue,java.lang.Boolean> confirmStateChange, androidx.compose.material.SnackbarHostState snackbarHostState);
+ method public void conceal(kotlin.jvm.functions.Function0<kotlin.Unit>? onConcealed = null);
+ method public androidx.compose.material.SnackbarHostState getSnackbarHostState();
+ method public boolean isConcealed();
+ method public boolean isRevealed();
+ method public void reveal(kotlin.jvm.functions.Function0<kotlin.Unit>? onRevealed = null);
+ property public final boolean isConcealed;
+ property public final boolean isRevealed;
+ field public static final androidx.compose.material.BackdropScaffoldState.Companion Companion;
+ }
+
+ public static final class BackdropScaffoldState.Companion {
+ method public androidx.compose.runtime.savedinstancestate.Saver<androidx.compose.material.BackdropScaffoldState,?> Saver(androidx.compose.animation.core.AnimationClockObservable clock, androidx.compose.animation.core.AnimationSpec<java.lang.Float> animationSpec, kotlin.jvm.functions.Function1<? super androidx.compose.material.BackdropValue,java.lang.Boolean> confirmStateChange, androidx.compose.material.SnackbarHostState snackbarHostState);
+ }
+
+ public enum BackdropValue {
+ method public static androidx.compose.material.BackdropValue valueOf(String name) throws java.lang.IllegalArgumentException;
+ method public static androidx.compose.material.BackdropValue[] values();
+ enum_constant public static final androidx.compose.material.BackdropValue Concealed;
+ enum_constant public static final androidx.compose.material.BackdropValue Revealed;
+ }
+
public final class BottomDrawerState extends androidx.compose.material.SwipeableState<androidx.compose.material.BottomDrawerValue> {
ctor public BottomDrawerState(androidx.compose.material.BottomDrawerValue initialValue, androidx.compose.animation.core.AnimationClockObservable clock, kotlin.jvm.functions.Function1<? super androidx.compose.material.BottomDrawerValue,java.lang.Boolean> confirmStateChange);
method public void close(kotlin.jvm.functions.Function0<kotlin.Unit>? onClosed = null);
@@ -262,8 +304,8 @@
}
public final class OutlinedTextFieldKt {
- method @androidx.compose.runtime.Composable public static void OutlinedTextField-Lv3QS5c(androidx.compose.ui.text.input.TextFieldValue value, kotlin.jvm.functions.Function1<? super androidx.compose.ui.text.input.TextFieldValue,kotlin.Unit> onValueChange, kotlin.jvm.functions.Function0<kotlin.Unit> label, androidx.compose.ui.Modifier modifier = Modifier, androidx.compose.ui.text.TextStyle textStyle = currentTextStyle(), kotlin.jvm.functions.Function0<kotlin.Unit>? placeholder = null, kotlin.jvm.functions.Function0<kotlin.Unit>? leadingIcon = null, kotlin.jvm.functions.Function0<kotlin.Unit>? trailingIcon = null, boolean isErrorValue = false, androidx.compose.ui.text.input.VisualTransformation visualTransformation = VisualTransformation.None, androidx.compose.ui.text.input.KeyboardType keyboardType = KeyboardType.Text, androidx.compose.ui.text.input.ImeAction imeAction = ImeAction.Unspecified, kotlin.jvm.functions.Function2<? super androidx.compose.ui.text.input.ImeAction,? super androidx.compose.ui.text.SoftwareKeyboardController,kotlin.Unit> onImeActionPerformed = { _, _ -> }, kotlin.jvm.functions.Function1<? super androidx.compose.ui.text.SoftwareKeyboardController,kotlin.Unit> onTextInputStarted = {}, long activeColor = MaterialTheme.colors.primary, long inactiveColor = MaterialTheme.colors.onSurface, long errorColor = MaterialTheme.colors.error);
- method @androidx.compose.runtime.Composable public static void OutlinedTextField-iUY4m8E(String value, kotlin.jvm.functions.Function1<? super java.lang.String,kotlin.Unit> onValueChange, kotlin.jvm.functions.Function0<kotlin.Unit> label, androidx.compose.ui.Modifier modifier = Modifier, androidx.compose.ui.text.TextStyle textStyle = currentTextStyle(), kotlin.jvm.functions.Function0<kotlin.Unit>? placeholder = null, kotlin.jvm.functions.Function0<kotlin.Unit>? leadingIcon = null, kotlin.jvm.functions.Function0<kotlin.Unit>? trailingIcon = null, boolean isErrorValue = false, androidx.compose.ui.text.input.VisualTransformation visualTransformation = VisualTransformation.None, androidx.compose.ui.text.input.KeyboardType keyboardType = KeyboardType.Text, androidx.compose.ui.text.input.ImeAction imeAction = ImeAction.Unspecified, kotlin.jvm.functions.Function2<? super androidx.compose.ui.text.input.ImeAction,? super androidx.compose.ui.text.SoftwareKeyboardController,kotlin.Unit> onImeActionPerformed = { _, _ -> }, kotlin.jvm.functions.Function1<? super androidx.compose.ui.text.SoftwareKeyboardController,kotlin.Unit> onTextInputStarted = {}, long activeColor = MaterialTheme.colors.primary, long inactiveColor = MaterialTheme.colors.onSurface, long errorColor = MaterialTheme.colors.error);
+ method @androidx.compose.runtime.Composable public static void OutlinedTextField--KhY4tc(androidx.compose.ui.text.input.TextFieldValue value, kotlin.jvm.functions.Function1<? super androidx.compose.ui.text.input.TextFieldValue,kotlin.Unit> onValueChange, androidx.compose.ui.Modifier modifier = Modifier, androidx.compose.ui.text.TextStyle textStyle = currentTextStyle(), kotlin.jvm.functions.Function0<kotlin.Unit>? label = null, kotlin.jvm.functions.Function0<kotlin.Unit>? placeholder = null, kotlin.jvm.functions.Function0<kotlin.Unit>? leadingIcon = null, kotlin.jvm.functions.Function0<kotlin.Unit>? trailingIcon = null, boolean isErrorValue = false, androidx.compose.ui.text.input.VisualTransformation visualTransformation = VisualTransformation.None, androidx.compose.ui.text.input.KeyboardType keyboardType = KeyboardType.Text, androidx.compose.ui.text.input.ImeAction imeAction = ImeAction.Unspecified, kotlin.jvm.functions.Function2<? super androidx.compose.ui.text.input.ImeAction,? super androidx.compose.ui.text.SoftwareKeyboardController,kotlin.Unit> onImeActionPerformed = { _, _ -> }, kotlin.jvm.functions.Function1<? super androidx.compose.ui.text.SoftwareKeyboardController,kotlin.Unit> onTextInputStarted = {}, long activeColor = MaterialTheme.colors.primary, long inactiveColor = MaterialTheme.colors.onSurface, long errorColor = MaterialTheme.colors.error);
+ method @androidx.compose.runtime.Composable public static void OutlinedTextField-YV8zA4E(String value, kotlin.jvm.functions.Function1<? super java.lang.String,kotlin.Unit> onValueChange, androidx.compose.ui.Modifier modifier = Modifier, androidx.compose.ui.text.TextStyle textStyle = currentTextStyle(), kotlin.jvm.functions.Function0<kotlin.Unit>? label = null, kotlin.jvm.functions.Function0<kotlin.Unit>? placeholder = null, kotlin.jvm.functions.Function0<kotlin.Unit>? leadingIcon = null, kotlin.jvm.functions.Function0<kotlin.Unit>? trailingIcon = null, boolean isErrorValue = false, androidx.compose.ui.text.input.VisualTransformation visualTransformation = VisualTransformation.None, androidx.compose.ui.text.input.KeyboardType keyboardType = KeyboardType.Text, androidx.compose.ui.text.input.ImeAction imeAction = ImeAction.Unspecified, kotlin.jvm.functions.Function2<? super androidx.compose.ui.text.input.ImeAction,? super androidx.compose.ui.text.SoftwareKeyboardController,kotlin.Unit> onImeActionPerformed = { _, _ -> }, kotlin.jvm.functions.Function1<? super androidx.compose.ui.text.SoftwareKeyboardController,kotlin.Unit> onTextInputStarted = {}, long activeColor = MaterialTheme.colors.primary, long inactiveColor = MaterialTheme.colors.onSurface, long errorColor = MaterialTheme.colors.error);
}
public final class ProgressIndicatorConstants {
@@ -486,8 +528,8 @@
}
public final class TextFieldKt {
- method @androidx.compose.runtime.Composable public static void TextField-CfoTMnE(String value, kotlin.jvm.functions.Function1<? super java.lang.String,kotlin.Unit> onValueChange, kotlin.jvm.functions.Function0<kotlin.Unit> label, androidx.compose.ui.Modifier modifier = Modifier, androidx.compose.ui.text.TextStyle textStyle = currentTextStyle(), kotlin.jvm.functions.Function0<kotlin.Unit>? placeholder = null, kotlin.jvm.functions.Function0<kotlin.Unit>? leadingIcon = null, kotlin.jvm.functions.Function0<kotlin.Unit>? trailingIcon = null, boolean isErrorValue = false, androidx.compose.ui.text.input.VisualTransformation visualTransformation = VisualTransformation.None, androidx.compose.ui.text.input.KeyboardType keyboardType = KeyboardType.Text, androidx.compose.ui.text.input.ImeAction imeAction = ImeAction.Unspecified, kotlin.jvm.functions.Function2<? super androidx.compose.ui.text.input.ImeAction,? super androidx.compose.ui.text.SoftwareKeyboardController,kotlin.Unit> onImeActionPerformed = { _, _ -> }, kotlin.jvm.functions.Function1<? super androidx.compose.ui.text.SoftwareKeyboardController,kotlin.Unit> onTextInputStarted = {}, long activeColor = MaterialTheme.colors.primary, long inactiveColor = MaterialTheme.colors.onSurface, long errorColor = MaterialTheme.colors.error, long backgroundColor = MaterialTheme.colors.onSurface, androidx.compose.ui.graphics.Shape shape = MaterialTheme.shapes.small.copy(ZeroCornerSize, ZeroCornerSize));
- method @androidx.compose.runtime.Composable public static void TextField-ToDxGl4(androidx.compose.ui.text.input.TextFieldValue value, kotlin.jvm.functions.Function1<? super androidx.compose.ui.text.input.TextFieldValue,kotlin.Unit> onValueChange, kotlin.jvm.functions.Function0<kotlin.Unit> label, androidx.compose.ui.Modifier modifier = Modifier, androidx.compose.ui.text.TextStyle textStyle = currentTextStyle(), kotlin.jvm.functions.Function0<kotlin.Unit>? placeholder = null, kotlin.jvm.functions.Function0<kotlin.Unit>? leadingIcon = null, kotlin.jvm.functions.Function0<kotlin.Unit>? trailingIcon = null, boolean isErrorValue = false, androidx.compose.ui.text.input.VisualTransformation visualTransformation = VisualTransformation.None, androidx.compose.ui.text.input.KeyboardType keyboardType = KeyboardType.Text, androidx.compose.ui.text.input.ImeAction imeAction = ImeAction.Unspecified, kotlin.jvm.functions.Function2<? super androidx.compose.ui.text.input.ImeAction,? super androidx.compose.ui.text.SoftwareKeyboardController,kotlin.Unit> onImeActionPerformed = { _, _ -> }, kotlin.jvm.functions.Function1<? super androidx.compose.ui.text.SoftwareKeyboardController,kotlin.Unit> onTextInputStarted = {}, long activeColor = MaterialTheme.colors.primary, long inactiveColor = MaterialTheme.colors.onSurface, long errorColor = MaterialTheme.colors.error, long backgroundColor = MaterialTheme.colors.onSurface, androidx.compose.ui.graphics.Shape shape = MaterialTheme.shapes.small.copy(ZeroCornerSize, ZeroCornerSize));
+ method @androidx.compose.runtime.Composable public static void TextField-enE39lU(String value, kotlin.jvm.functions.Function1<? super java.lang.String,kotlin.Unit> onValueChange, androidx.compose.ui.Modifier modifier = Modifier, androidx.compose.ui.text.TextStyle textStyle = currentTextStyle(), kotlin.jvm.functions.Function0<kotlin.Unit>? label = null, kotlin.jvm.functions.Function0<kotlin.Unit>? placeholder = null, kotlin.jvm.functions.Function0<kotlin.Unit>? leadingIcon = null, kotlin.jvm.functions.Function0<kotlin.Unit>? trailingIcon = null, boolean isErrorValue = false, androidx.compose.ui.text.input.VisualTransformation visualTransformation = VisualTransformation.None, androidx.compose.ui.text.input.KeyboardType keyboardType = KeyboardType.Text, androidx.compose.ui.text.input.ImeAction imeAction = ImeAction.Unspecified, kotlin.jvm.functions.Function2<? super androidx.compose.ui.text.input.ImeAction,? super androidx.compose.ui.text.SoftwareKeyboardController,kotlin.Unit> onImeActionPerformed = { _, _ -> }, kotlin.jvm.functions.Function1<? super androidx.compose.ui.text.SoftwareKeyboardController,kotlin.Unit> onTextInputStarted = {}, long activeColor = MaterialTheme.colors.primary, long inactiveColor = MaterialTheme.colors.onSurface, long errorColor = MaterialTheme.colors.error, long backgroundColor = MaterialTheme.colors.onSurface, androidx.compose.ui.graphics.Shape shape = MaterialTheme.shapes.small.copy(ZeroCornerSize, ZeroCornerSize));
+ method @androidx.compose.runtime.Composable public static void TextField-tHV9mug(androidx.compose.ui.text.input.TextFieldValue value, kotlin.jvm.functions.Function1<? super androidx.compose.ui.text.input.TextFieldValue,kotlin.Unit> onValueChange, androidx.compose.ui.Modifier modifier = Modifier, androidx.compose.ui.text.TextStyle textStyle = currentTextStyle(), kotlin.jvm.functions.Function0<kotlin.Unit>? label = null, kotlin.jvm.functions.Function0<kotlin.Unit>? placeholder = null, kotlin.jvm.functions.Function0<kotlin.Unit>? leadingIcon = null, kotlin.jvm.functions.Function0<kotlin.Unit>? trailingIcon = null, boolean isErrorValue = false, androidx.compose.ui.text.input.VisualTransformation visualTransformation = VisualTransformation.None, androidx.compose.ui.text.input.KeyboardType keyboardType = KeyboardType.Text, androidx.compose.ui.text.input.ImeAction imeAction = ImeAction.Unspecified, kotlin.jvm.functions.Function2<? super androidx.compose.ui.text.input.ImeAction,? super androidx.compose.ui.text.SoftwareKeyboardController,kotlin.Unit> onImeActionPerformed = { _, _ -> }, kotlin.jvm.functions.Function1<? super androidx.compose.ui.text.SoftwareKeyboardController,kotlin.Unit> onTextInputStarted = {}, long activeColor = MaterialTheme.colors.primary, long inactiveColor = MaterialTheme.colors.onSurface, long errorColor = MaterialTheme.colors.error, long backgroundColor = MaterialTheme.colors.onSurface, androidx.compose.ui.graphics.Shape shape = MaterialTheme.shapes.small.copy(ZeroCornerSize, ZeroCornerSize));
}
@androidx.compose.material.ExperimentalMaterialApi @androidx.compose.runtime.Stable public interface ThresholdConfig {
diff --git a/compose/material/material/api/restricted_current.txt b/compose/material/material/api/restricted_current.txt
index 009630d..387e97b 100644
--- a/compose/material/material/api/restricted_current.txt
+++ b/compose/material/material/api/restricted_current.txt
@@ -12,6 +12,48 @@
method @androidx.compose.runtime.Composable public static void TopAppBar-ye6PvEY(androidx.compose.ui.Modifier modifier = Modifier, long backgroundColor = MaterialTheme.colors.primarySurface, long contentColor = contentColorFor(backgroundColor), float elevation = androidx.compose.material.AppBarKt.TopAppBarElevation, kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.RowScope,kotlin.Unit> content);
}
+ public final class BackdropConstants {
+ method public float getDefaultFrontLayerElevation();
+ method public long getDefaultFrontLayerScrimColor();
+ method public androidx.compose.ui.graphics.Shape getDefaultFrontLayerShape();
+ method public float getDefaultHeaderHeight();
+ method public float getDefaultPeekHeight();
+ property public final float DefaultFrontLayerElevation;
+ property public final long DefaultFrontLayerScrimColor;
+ property public final androidx.compose.ui.graphics.Shape DefaultFrontLayerShape;
+ property public final float DefaultHeaderHeight;
+ property public final float DefaultPeekHeight;
+ field public static final androidx.compose.material.BackdropConstants INSTANCE;
+ }
+
+ public final class BackdropScaffoldKt {
+ method @androidx.compose.material.ExperimentalMaterialApi @androidx.compose.runtime.Composable public static void BackdropScaffold-n7o2bDw(androidx.compose.ui.Modifier modifier = Modifier, androidx.compose.material.BackdropScaffoldState backdropScaffoldState = rememberBackdropState(Concealed), boolean gesturesEnabled = true, float peekHeight = BackdropConstants.DefaultPeekHeight, float headerHeight = BackdropConstants.DefaultHeaderHeight, boolean persistentAppBar = true, boolean stickyFrontLayer = true, long backLayerBackgroundColor = MaterialTheme.colors.primary, long backLayerContentColor = contentColorFor(backLayerBackgroundColor), androidx.compose.ui.graphics.Shape frontLayerShape = BackdropConstants.DefaultFrontLayerShape, float frontLayerElevation = BackdropConstants.DefaultFrontLayerElevation, long frontLayerBackgroundColor = MaterialTheme.colors.surface, long frontLayerContentColor = contentColorFor(frontLayerBackgroundColor), long frontLayerScrimColor = BackdropConstants.DefaultFrontLayerScrimColor, kotlin.jvm.functions.Function1<? super androidx.compose.material.SnackbarHostState,kotlin.Unit> snackbarHost = { SnackbarHost(it) }, kotlin.jvm.functions.Function0<kotlin.Unit> appBar, kotlin.jvm.functions.Function0<kotlin.Unit> backLayerContent, kotlin.jvm.functions.Function0<kotlin.Unit> frontLayerContent);
+ method @androidx.compose.material.ExperimentalMaterialApi @androidx.compose.runtime.Composable public static androidx.compose.material.BackdropScaffoldState rememberBackdropState(androidx.compose.material.BackdropValue initialValue, androidx.compose.animation.core.AnimationSpec<java.lang.Float> animationSpec = SwipeableConstants.DefaultAnimationSpec, kotlin.jvm.functions.Function1<? super androidx.compose.material.BackdropValue,java.lang.Boolean> confirmStateChange = { return true }, androidx.compose.material.SnackbarHostState snackbarHostState = androidx.compose.material.SnackbarHostState());
+ }
+
+ @androidx.compose.material.ExperimentalMaterialApi public final class BackdropScaffoldState extends androidx.compose.material.SwipeableState<androidx.compose.material.BackdropValue> {
+ ctor public BackdropScaffoldState(androidx.compose.material.BackdropValue initialValue, androidx.compose.animation.core.AnimationClockObservable clock, androidx.compose.animation.core.AnimationSpec<java.lang.Float> animationSpec, kotlin.jvm.functions.Function1<? super androidx.compose.material.BackdropValue,java.lang.Boolean> confirmStateChange, androidx.compose.material.SnackbarHostState snackbarHostState);
+ method public void conceal(kotlin.jvm.functions.Function0<kotlin.Unit>? onConcealed = null);
+ method public androidx.compose.material.SnackbarHostState getSnackbarHostState();
+ method public boolean isConcealed();
+ method public boolean isRevealed();
+ method public void reveal(kotlin.jvm.functions.Function0<kotlin.Unit>? onRevealed = null);
+ property public final boolean isConcealed;
+ property public final boolean isRevealed;
+ field public static final androidx.compose.material.BackdropScaffoldState.Companion Companion;
+ }
+
+ public static final class BackdropScaffoldState.Companion {
+ method public androidx.compose.runtime.savedinstancestate.Saver<androidx.compose.material.BackdropScaffoldState,?> Saver(androidx.compose.animation.core.AnimationClockObservable clock, androidx.compose.animation.core.AnimationSpec<java.lang.Float> animationSpec, kotlin.jvm.functions.Function1<? super androidx.compose.material.BackdropValue,java.lang.Boolean> confirmStateChange, androidx.compose.material.SnackbarHostState snackbarHostState);
+ }
+
+ public enum BackdropValue {
+ method public static androidx.compose.material.BackdropValue valueOf(String name) throws java.lang.IllegalArgumentException;
+ method public static androidx.compose.material.BackdropValue[] values();
+ enum_constant public static final androidx.compose.material.BackdropValue Concealed;
+ enum_constant public static final androidx.compose.material.BackdropValue Revealed;
+ }
+
public final class BottomDrawerState extends androidx.compose.material.SwipeableState<androidx.compose.material.BottomDrawerValue> {
ctor public BottomDrawerState(androidx.compose.material.BottomDrawerValue initialValue, androidx.compose.animation.core.AnimationClockObservable clock, kotlin.jvm.functions.Function1<? super androidx.compose.material.BottomDrawerValue,java.lang.Boolean> confirmStateChange);
method public void close(kotlin.jvm.functions.Function0<kotlin.Unit>? onClosed = null);
@@ -262,8 +304,8 @@
}
public final class OutlinedTextFieldKt {
- method @androidx.compose.runtime.Composable public static void OutlinedTextField-Lv3QS5c(androidx.compose.ui.text.input.TextFieldValue value, kotlin.jvm.functions.Function1<? super androidx.compose.ui.text.input.TextFieldValue,kotlin.Unit> onValueChange, kotlin.jvm.functions.Function0<kotlin.Unit> label, androidx.compose.ui.Modifier modifier = Modifier, androidx.compose.ui.text.TextStyle textStyle = currentTextStyle(), kotlin.jvm.functions.Function0<kotlin.Unit>? placeholder = null, kotlin.jvm.functions.Function0<kotlin.Unit>? leadingIcon = null, kotlin.jvm.functions.Function0<kotlin.Unit>? trailingIcon = null, boolean isErrorValue = false, androidx.compose.ui.text.input.VisualTransformation visualTransformation = VisualTransformation.None, androidx.compose.ui.text.input.KeyboardType keyboardType = KeyboardType.Text, androidx.compose.ui.text.input.ImeAction imeAction = ImeAction.Unspecified, kotlin.jvm.functions.Function2<? super androidx.compose.ui.text.input.ImeAction,? super androidx.compose.ui.text.SoftwareKeyboardController,kotlin.Unit> onImeActionPerformed = { _, _ -> }, kotlin.jvm.functions.Function1<? super androidx.compose.ui.text.SoftwareKeyboardController,kotlin.Unit> onTextInputStarted = {}, long activeColor = MaterialTheme.colors.primary, long inactiveColor = MaterialTheme.colors.onSurface, long errorColor = MaterialTheme.colors.error);
- method @androidx.compose.runtime.Composable public static void OutlinedTextField-iUY4m8E(String value, kotlin.jvm.functions.Function1<? super java.lang.String,kotlin.Unit> onValueChange, kotlin.jvm.functions.Function0<kotlin.Unit> label, androidx.compose.ui.Modifier modifier = Modifier, androidx.compose.ui.text.TextStyle textStyle = currentTextStyle(), kotlin.jvm.functions.Function0<kotlin.Unit>? placeholder = null, kotlin.jvm.functions.Function0<kotlin.Unit>? leadingIcon = null, kotlin.jvm.functions.Function0<kotlin.Unit>? trailingIcon = null, boolean isErrorValue = false, androidx.compose.ui.text.input.VisualTransformation visualTransformation = VisualTransformation.None, androidx.compose.ui.text.input.KeyboardType keyboardType = KeyboardType.Text, androidx.compose.ui.text.input.ImeAction imeAction = ImeAction.Unspecified, kotlin.jvm.functions.Function2<? super androidx.compose.ui.text.input.ImeAction,? super androidx.compose.ui.text.SoftwareKeyboardController,kotlin.Unit> onImeActionPerformed = { _, _ -> }, kotlin.jvm.functions.Function1<? super androidx.compose.ui.text.SoftwareKeyboardController,kotlin.Unit> onTextInputStarted = {}, long activeColor = MaterialTheme.colors.primary, long inactiveColor = MaterialTheme.colors.onSurface, long errorColor = MaterialTheme.colors.error);
+ method @androidx.compose.runtime.Composable public static void OutlinedTextField--KhY4tc(androidx.compose.ui.text.input.TextFieldValue value, kotlin.jvm.functions.Function1<? super androidx.compose.ui.text.input.TextFieldValue,kotlin.Unit> onValueChange, androidx.compose.ui.Modifier modifier = Modifier, androidx.compose.ui.text.TextStyle textStyle = currentTextStyle(), kotlin.jvm.functions.Function0<kotlin.Unit>? label = null, kotlin.jvm.functions.Function0<kotlin.Unit>? placeholder = null, kotlin.jvm.functions.Function0<kotlin.Unit>? leadingIcon = null, kotlin.jvm.functions.Function0<kotlin.Unit>? trailingIcon = null, boolean isErrorValue = false, androidx.compose.ui.text.input.VisualTransformation visualTransformation = VisualTransformation.None, androidx.compose.ui.text.input.KeyboardType keyboardType = KeyboardType.Text, androidx.compose.ui.text.input.ImeAction imeAction = ImeAction.Unspecified, kotlin.jvm.functions.Function2<? super androidx.compose.ui.text.input.ImeAction,? super androidx.compose.ui.text.SoftwareKeyboardController,kotlin.Unit> onImeActionPerformed = { _, _ -> }, kotlin.jvm.functions.Function1<? super androidx.compose.ui.text.SoftwareKeyboardController,kotlin.Unit> onTextInputStarted = {}, long activeColor = MaterialTheme.colors.primary, long inactiveColor = MaterialTheme.colors.onSurface, long errorColor = MaterialTheme.colors.error);
+ method @androidx.compose.runtime.Composable public static void OutlinedTextField-YV8zA4E(String value, kotlin.jvm.functions.Function1<? super java.lang.String,kotlin.Unit> onValueChange, androidx.compose.ui.Modifier modifier = Modifier, androidx.compose.ui.text.TextStyle textStyle = currentTextStyle(), kotlin.jvm.functions.Function0<kotlin.Unit>? label = null, kotlin.jvm.functions.Function0<kotlin.Unit>? placeholder = null, kotlin.jvm.functions.Function0<kotlin.Unit>? leadingIcon = null, kotlin.jvm.functions.Function0<kotlin.Unit>? trailingIcon = null, boolean isErrorValue = false, androidx.compose.ui.text.input.VisualTransformation visualTransformation = VisualTransformation.None, androidx.compose.ui.text.input.KeyboardType keyboardType = KeyboardType.Text, androidx.compose.ui.text.input.ImeAction imeAction = ImeAction.Unspecified, kotlin.jvm.functions.Function2<? super androidx.compose.ui.text.input.ImeAction,? super androidx.compose.ui.text.SoftwareKeyboardController,kotlin.Unit> onImeActionPerformed = { _, _ -> }, kotlin.jvm.functions.Function1<? super androidx.compose.ui.text.SoftwareKeyboardController,kotlin.Unit> onTextInputStarted = {}, long activeColor = MaterialTheme.colors.primary, long inactiveColor = MaterialTheme.colors.onSurface, long errorColor = MaterialTheme.colors.error);
}
public final class ProgressIndicatorConstants {
@@ -486,8 +528,8 @@
}
public final class TextFieldKt {
- method @androidx.compose.runtime.Composable public static void TextField-CfoTMnE(String value, kotlin.jvm.functions.Function1<? super java.lang.String,kotlin.Unit> onValueChange, kotlin.jvm.functions.Function0<kotlin.Unit> label, androidx.compose.ui.Modifier modifier = Modifier, androidx.compose.ui.text.TextStyle textStyle = currentTextStyle(), kotlin.jvm.functions.Function0<kotlin.Unit>? placeholder = null, kotlin.jvm.functions.Function0<kotlin.Unit>? leadingIcon = null, kotlin.jvm.functions.Function0<kotlin.Unit>? trailingIcon = null, boolean isErrorValue = false, androidx.compose.ui.text.input.VisualTransformation visualTransformation = VisualTransformation.None, androidx.compose.ui.text.input.KeyboardType keyboardType = KeyboardType.Text, androidx.compose.ui.text.input.ImeAction imeAction = ImeAction.Unspecified, kotlin.jvm.functions.Function2<? super androidx.compose.ui.text.input.ImeAction,? super androidx.compose.ui.text.SoftwareKeyboardController,kotlin.Unit> onImeActionPerformed = { _, _ -> }, kotlin.jvm.functions.Function1<? super androidx.compose.ui.text.SoftwareKeyboardController,kotlin.Unit> onTextInputStarted = {}, long activeColor = MaterialTheme.colors.primary, long inactiveColor = MaterialTheme.colors.onSurface, long errorColor = MaterialTheme.colors.error, long backgroundColor = MaterialTheme.colors.onSurface, androidx.compose.ui.graphics.Shape shape = MaterialTheme.shapes.small.copy(ZeroCornerSize, ZeroCornerSize));
- method @androidx.compose.runtime.Composable public static void TextField-ToDxGl4(androidx.compose.ui.text.input.TextFieldValue value, kotlin.jvm.functions.Function1<? super androidx.compose.ui.text.input.TextFieldValue,kotlin.Unit> onValueChange, kotlin.jvm.functions.Function0<kotlin.Unit> label, androidx.compose.ui.Modifier modifier = Modifier, androidx.compose.ui.text.TextStyle textStyle = currentTextStyle(), kotlin.jvm.functions.Function0<kotlin.Unit>? placeholder = null, kotlin.jvm.functions.Function0<kotlin.Unit>? leadingIcon = null, kotlin.jvm.functions.Function0<kotlin.Unit>? trailingIcon = null, boolean isErrorValue = false, androidx.compose.ui.text.input.VisualTransformation visualTransformation = VisualTransformation.None, androidx.compose.ui.text.input.KeyboardType keyboardType = KeyboardType.Text, androidx.compose.ui.text.input.ImeAction imeAction = ImeAction.Unspecified, kotlin.jvm.functions.Function2<? super androidx.compose.ui.text.input.ImeAction,? super androidx.compose.ui.text.SoftwareKeyboardController,kotlin.Unit> onImeActionPerformed = { _, _ -> }, kotlin.jvm.functions.Function1<? super androidx.compose.ui.text.SoftwareKeyboardController,kotlin.Unit> onTextInputStarted = {}, long activeColor = MaterialTheme.colors.primary, long inactiveColor = MaterialTheme.colors.onSurface, long errorColor = MaterialTheme.colors.error, long backgroundColor = MaterialTheme.colors.onSurface, androidx.compose.ui.graphics.Shape shape = MaterialTheme.shapes.small.copy(ZeroCornerSize, ZeroCornerSize));
+ method @androidx.compose.runtime.Composable public static void TextField-enE39lU(String value, kotlin.jvm.functions.Function1<? super java.lang.String,kotlin.Unit> onValueChange, androidx.compose.ui.Modifier modifier = Modifier, androidx.compose.ui.text.TextStyle textStyle = currentTextStyle(), kotlin.jvm.functions.Function0<kotlin.Unit>? label = null, kotlin.jvm.functions.Function0<kotlin.Unit>? placeholder = null, kotlin.jvm.functions.Function0<kotlin.Unit>? leadingIcon = null, kotlin.jvm.functions.Function0<kotlin.Unit>? trailingIcon = null, boolean isErrorValue = false, androidx.compose.ui.text.input.VisualTransformation visualTransformation = VisualTransformation.None, androidx.compose.ui.text.input.KeyboardType keyboardType = KeyboardType.Text, androidx.compose.ui.text.input.ImeAction imeAction = ImeAction.Unspecified, kotlin.jvm.functions.Function2<? super androidx.compose.ui.text.input.ImeAction,? super androidx.compose.ui.text.SoftwareKeyboardController,kotlin.Unit> onImeActionPerformed = { _, _ -> }, kotlin.jvm.functions.Function1<? super androidx.compose.ui.text.SoftwareKeyboardController,kotlin.Unit> onTextInputStarted = {}, long activeColor = MaterialTheme.colors.primary, long inactiveColor = MaterialTheme.colors.onSurface, long errorColor = MaterialTheme.colors.error, long backgroundColor = MaterialTheme.colors.onSurface, androidx.compose.ui.graphics.Shape shape = MaterialTheme.shapes.small.copy(ZeroCornerSize, ZeroCornerSize));
+ method @androidx.compose.runtime.Composable public static void TextField-tHV9mug(androidx.compose.ui.text.input.TextFieldValue value, kotlin.jvm.functions.Function1<? super androidx.compose.ui.text.input.TextFieldValue,kotlin.Unit> onValueChange, androidx.compose.ui.Modifier modifier = Modifier, androidx.compose.ui.text.TextStyle textStyle = currentTextStyle(), kotlin.jvm.functions.Function0<kotlin.Unit>? label = null, kotlin.jvm.functions.Function0<kotlin.Unit>? placeholder = null, kotlin.jvm.functions.Function0<kotlin.Unit>? leadingIcon = null, kotlin.jvm.functions.Function0<kotlin.Unit>? trailingIcon = null, boolean isErrorValue = false, androidx.compose.ui.text.input.VisualTransformation visualTransformation = VisualTransformation.None, androidx.compose.ui.text.input.KeyboardType keyboardType = KeyboardType.Text, androidx.compose.ui.text.input.ImeAction imeAction = ImeAction.Unspecified, kotlin.jvm.functions.Function2<? super androidx.compose.ui.text.input.ImeAction,? super androidx.compose.ui.text.SoftwareKeyboardController,kotlin.Unit> onImeActionPerformed = { _, _ -> }, kotlin.jvm.functions.Function1<? super androidx.compose.ui.text.SoftwareKeyboardController,kotlin.Unit> onTextInputStarted = {}, long activeColor = MaterialTheme.colors.primary, long inactiveColor = MaterialTheme.colors.onSurface, long errorColor = MaterialTheme.colors.error, long backgroundColor = MaterialTheme.colors.onSurface, androidx.compose.ui.graphics.Shape shape = MaterialTheme.shapes.small.copy(ZeroCornerSize, ZeroCornerSize));
}
@androidx.compose.material.ExperimentalMaterialApi @androidx.compose.runtime.Stable public interface ThresholdConfig {
diff --git a/compose/material/material/build.gradle b/compose/material/material/build.gradle
index 1a4736e..ef5e787 100644
--- a/compose/material/material/build.gradle
+++ b/compose/material/material/build.gradle
@@ -89,7 +89,7 @@
androidx {
name = "Compose Material Components"
publish = Publish.SNAPSHOT_AND_RELEASE
- mavenVersion = LibraryVersions.UI
+ mavenVersion = LibraryVersions.COMPOSE
mavenGroup = LibraryGroups.Compose.MATERIAL
inceptionYear = "2018"
description = "Compose Material Design Components library"
diff --git a/compose/material/material/icons/generator/build.gradle b/compose/material/material/icons/generator/build.gradle
index 20f02c2..eeee7f6 100644
--- a/compose/material/material/icons/generator/build.gradle
+++ b/compose/material/material/icons/generator/build.gradle
@@ -45,7 +45,7 @@
name = "Material Icon Generator"
publish = Publish.NONE
toolingProject = true
- mavenVersion = LibraryVersions.UI
+ mavenVersion = LibraryVersions.COMPOSE
mavenGroup = LibraryGroups.Compose.MATERIAL
inceptionYear = "2020"
description = "Generator module that parses XML drawables to generate programmatic " +
diff --git a/compose/material/material/icons/generator/src/main/kotlin/androidx/compose/material/icons/generator/IconParser.kt b/compose/material/material/icons/generator/src/main/kotlin/androidx/compose/material/icons/generator/IconParser.kt
index 5422275..3bc28e4 100644
--- a/compose/material/material/icons/generator/src/main/kotlin/androidx/compose/material/icons/generator/IconParser.kt
+++ b/compose/material/material/icons/generator/src/main/kotlin/androidx/compose/material/icons/generator/IconParser.kt
@@ -16,6 +16,7 @@
package androidx.compose.material.icons.generator
+import androidx.compose.material.icons.generator.vector.FillType
import androidx.compose.material.icons.generator.vector.PathParser
import androidx.compose.material.icons.generator.vector.Vector
import androidx.compose.material.icons.generator.vector.VectorNode
@@ -59,12 +60,18 @@
)
val fillAlpha = parser.getValueAsFloat(FILL_ALPHA)
val strokeAlpha = parser.getValueAsFloat(STROKE_ALPHA)
- val path =
- VectorNode.Path(
- strokeAlpha = strokeAlpha ?: 1f,
- fillAlpha = fillAlpha ?: 1f,
- nodes = PathParser.parsePathString(pathData)
- )
+ val fillType = when (parser.getAttributeValue(null, FILL_TYPE)) {
+ // evenOdd and nonZero are the only supported values here, where
+ // nonZero is the default if no values are defined.
+ EVEN_ODD -> FillType.EvenOdd
+ else -> FillType.NonZero
+ }
+ val path = VectorNode.Path(
+ strokeAlpha = strokeAlpha ?: 1f,
+ fillAlpha = fillAlpha ?: 1f,
+ fillType = fillType,
+ nodes = PathParser.parsePathString(pathData)
+ )
if (currentGroup != null) {
currentGroup.paths.add(path)
} else {
@@ -119,3 +126,7 @@
private const val PATH_DATA = "android:pathData"
private const val FILL_ALPHA = "android:fillAlpha"
private const val STROKE_ALPHA = "android:strokeAlpha"
+private const val FILL_TYPE = "android:fillType"
+
+// XML attribute values
+private const val EVEN_ODD = "evenOdd"
diff --git a/compose/material/material/icons/generator/src/main/kotlin/androidx/compose/material/icons/generator/Names.kt b/compose/material/material/icons/generator/src/main/kotlin/androidx/compose/material/icons/generator/Names.kt
index 76b6f88..5990cf9 100644
--- a/compose/material/material/icons/generator/src/main/kotlin/androidx/compose/material/icons/generator/Names.kt
+++ b/compose/material/material/icons/generator/src/main/kotlin/androidx/compose/material/icons/generator/Names.kt
@@ -24,7 +24,8 @@
*/
enum class PackageNames(val packageName: String) {
MaterialIconsPackage("androidx.compose.material.icons"),
- VectorPackage("androidx.compose.ui.graphics.vector")
+ GraphicsPackage("androidx.compose.ui.graphics"),
+ VectorPackage(GraphicsPackage.packageName + ".vector")
}
/**
@@ -33,6 +34,7 @@
object ClassNames {
val Icons = PackageNames.MaterialIconsPackage.className("Icons")
val VectorAsset = PackageNames.VectorPackage.className("VectorAsset")
+ val PathFillType = PackageNames.GraphicsPackage.className("PathFillType")
}
/**
@@ -42,6 +44,7 @@
val MaterialIcon = MemberName(PackageNames.MaterialIconsPackage.packageName, "materialIcon")
val MaterialPath = MemberName(PackageNames.MaterialIconsPackage.packageName, "materialPath")
+ val EvenOdd = MemberName(ClassNames.PathFillType, "EvenOdd")
val Group = MemberName(PackageNames.VectorPackage.packageName, "group")
}
diff --git a/compose/material/material/icons/generator/src/main/kotlin/androidx/compose/material/icons/generator/VectorAssetGenerator.kt b/compose/material/material/icons/generator/src/main/kotlin/androidx/compose/material/icons/generator/VectorAssetGenerator.kt
index b2e1118..076e9eb 100644
--- a/compose/material/material/icons/generator/src/main/kotlin/androidx/compose/material/icons/generator/VectorAssetGenerator.kt
+++ b/compose/material/material/icons/generator/src/main/kotlin/androidx/compose/material/icons/generator/VectorAssetGenerator.kt
@@ -16,13 +16,13 @@
package androidx.compose.material.icons.generator
+import androidx.compose.material.icons.generator.vector.FillType
import androidx.compose.material.icons.generator.vector.Vector
import androidx.compose.material.icons.generator.vector.VectorNode
import com.squareup.kotlinpoet.CodeBlock
import com.squareup.kotlinpoet.FileSpec
import com.squareup.kotlinpoet.FunSpec
import com.squareup.kotlinpoet.KModifier
-import com.squareup.kotlinpoet.MemberName
import com.squareup.kotlinpoet.PropertySpec
import com.squareup.kotlinpoet.buildCodeBlock
import java.util.Locale
@@ -118,14 +118,14 @@
when (vectorNode) {
// TODO: b/147418351 - add clip-paths once they are supported
is VectorNode.Group -> {
- addFunctionWithLambda(MemberNames.Group) {
- vectorNode.paths.forEach { path ->
- addRecursively(path)
- }
+ beginControlFlow("%M", MemberNames.Group)
+ vectorNode.paths.forEach { path ->
+ addRecursively(path)
}
+ endControlFlow()
}
is VectorNode.Path -> {
- addFunctionWithLambda(MemberNames.MaterialPath, vectorNode.getParameters()) {
+ addPath(vectorNode) {
vectorNode.nodes.forEach { pathNode ->
addStatement(pathNode.asFunctionCall())
}
@@ -135,36 +135,35 @@
}
/**
- * @return a [String] representing the parameters to add to the path function call to create the
- * correct path.
+ * Adds a function call to create the given [path], with [pathBody] containing the commands for
+ * the path.
*/
-private fun VectorNode.Path.getParameters(): String {
- val parameterList = listOfNotNull(
- "fillAlpha = ${fillAlpha}f".takeIf { fillAlpha != 1f },
- "strokeAlpha = ${strokeAlpha}f".takeIf { strokeAlpha != 1f }
- )
+private fun CodeBlock.Builder.addPath(
+ path: VectorNode.Path,
+ pathBody: CodeBlock.Builder.() -> Unit
+) {
+ // Only set the fill type if it is EvenOdd - otherwise it will just be the default.
+ val setFillType = path.fillType == FillType.EvenOdd
- return if (parameterList.isNotEmpty()) {
+ val parameterList = with(path) {
+ listOfNotNull(
+ "fillAlpha = ${fillAlpha}f".takeIf { fillAlpha != 1f },
+ "strokeAlpha = ${strokeAlpha}f".takeIf { strokeAlpha != 1f },
+ "pathFillType = %M".takeIf { setFillType }
+ )
+ }
+
+ val parameters = if (parameterList.isNotEmpty()) {
parameterList.joinToString(prefix = "(", postfix = ")")
} else {
""
}
-}
-/**
- * Generates a correctly indented lambda invocation for the given [function] and [lambdaBody].
- *
- * @param function [MemberName] of the function call
- * @param parameters string representing the parameters of this function call, e.g. "(1, 3)".
- * @param lambdaBody body for the trailing lambda of this function call
- *
- */
-private fun CodeBlock.Builder.addFunctionWithLambda(
- function: MemberName,
- parameters: String = "",
- lambdaBody: CodeBlock.Builder.() -> Unit
-) {
- beginControlFlow("%M$parameters", function)
- lambdaBody()
+ if (setFillType) {
+ beginControlFlow("%M$parameters", MemberNames.MaterialPath, MemberNames.EvenOdd)
+ } else {
+ beginControlFlow("%M$parameters", MemberNames.MaterialPath)
+ }
+ pathBody()
endControlFlow()
}
diff --git a/appsearch/appsearch/src/main/java/androidx/appsearch/impl/package-info.java b/compose/material/material/icons/generator/src/main/kotlin/androidx/compose/material/icons/generator/vector/FillType.kt
similarity index 64%
copy from appsearch/appsearch/src/main/java/androidx/appsearch/impl/package-info.java
copy to compose/material/material/icons/generator/src/main/kotlin/androidx/compose/material/icons/generator/vector/FillType.kt
index 68c3b98..7dbfdbc 100644
--- a/appsearch/appsearch/src/main/java/androidx/appsearch/impl/package-info.java
+++ b/compose/material/material/icons/generator/src/main/kotlin/androidx/compose/material/icons/generator/vector/FillType.kt
@@ -14,10 +14,15 @@
* limitations under the License.
*/
-/**
- * @hide
- */
-@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
-package androidx.appsearch.impl;
+package androidx.compose.material.icons.generator.vector
-import androidx.annotation.RestrictTo;
+/**
+ * Determines the winding rule that decides how the interior of a [VectorNode.Path] is calculated.
+ *
+ * This maps to [android.graphics.Path.FillType] used in the framework, and can be defined in XML
+ * via `android:fillType`.
+ */
+enum class FillType {
+ NonZero,
+ EvenOdd
+}
diff --git a/compose/material/material/icons/generator/src/main/kotlin/androidx/compose/material/icons/generator/vector/Vector.kt b/compose/material/material/icons/generator/src/main/kotlin/androidx/compose/material/icons/generator/vector/Vector.kt
index a77a5c9..edb90ad 100644
--- a/compose/material/material/icons/generator/src/main/kotlin/androidx/compose/material/icons/generator/vector/Vector.kt
+++ b/compose/material/material/icons/generator/src/main/kotlin/androidx/compose/material/icons/generator/vector/Vector.kt
@@ -33,6 +33,7 @@
class Path(
val strokeAlpha: Float,
val fillAlpha: Float,
+ val fillType: FillType,
val nodes: List<PathNode>
) : VectorNode()
}
diff --git a/compose/material/material/icons/generator/src/test/kotlin/androidx/compose/material/icons/generator/IconParserTest.kt b/compose/material/material/icons/generator/src/test/kotlin/androidx/compose/material/icons/generator/IconParserTest.kt
index a73feeb..8676ea2 100644
--- a/compose/material/material/icons/generator/src/test/kotlin/androidx/compose/material/icons/generator/IconParserTest.kt
+++ b/compose/material/material/icons/generator/src/test/kotlin/androidx/compose/material/icons/generator/IconParserTest.kt
@@ -16,6 +16,7 @@
package androidx.compose.material.icons.generator
+import androidx.compose.material.icons.generator.vector.FillType
import androidx.compose.material.icons.generator.vector.PathNode
import androidx.compose.material.icons.generator.vector.VectorNode
import com.google.common.truth.Truth
@@ -40,6 +41,7 @@
val firstPath = nodes[0] as VectorNode.Path
Truth.assertThat(firstPath.fillAlpha).isEqualTo(0.3f)
Truth.assertThat(firstPath.strokeAlpha).isEqualTo(1f)
+ Truth.assertThat(firstPath.fillType).isEqualTo(FillType.NonZero)
val expectedFirstPathNodes = listOf(
PathNode.MoveTo(20f, 10f),
@@ -53,6 +55,7 @@
val secondPath = nodes[1] as VectorNode.Path
Truth.assertThat(secondPath.fillAlpha).isEqualTo(1f)
Truth.assertThat(secondPath.strokeAlpha).isEqualTo(0.9f)
+ Truth.assertThat(secondPath.fillType).isEqualTo(FillType.EvenOdd)
val expectedSecondPathNodes = listOf(
PathNode.MoveTo(16.5f, 9.0f),
@@ -74,6 +77,7 @@
android:pathData="M20,10, l10,10 0,10 -10, 0z" />
<path
android:strokeAlpha=".9"
- android:pathData="M16.5,9h3.5v9h-3.5z" />
+ android:pathData="M16.5,9h3.5v9h-3.5z"
+ android:fillType="evenOdd" />
</vector>
""".trimIndent()
diff --git a/compose/material/material/icons/generator/src/test/kotlin/androidx/compose/material/icons/generator/VectorAssetGeneratorTest.kt b/compose/material/material/icons/generator/src/test/kotlin/androidx/compose/material/icons/generator/VectorAssetGeneratorTest.kt
index 8a38551..e0a001e 100644
--- a/compose/material/material/icons/generator/src/test/kotlin/androidx/compose/material/icons/generator/VectorAssetGeneratorTest.kt
+++ b/compose/material/material/icons/generator/src/test/kotlin/androidx/compose/material/icons/generator/VectorAssetGeneratorTest.kt
@@ -17,6 +17,7 @@
package androidx.compose.material.icons.generator
import androidx.compose.material.icons.generator.PackageNames.MaterialIconsPackage
+import androidx.compose.material.icons.generator.vector.FillType
import androidx.compose.material.icons.generator.vector.PathNode
import androidx.compose.material.icons.generator.vector.Vector
import androidx.compose.material.icons.generator.vector.VectorNode
@@ -67,6 +68,7 @@
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.materialIcon
import androidx.compose.material.icons.materialPath
+ import androidx.compose.ui.graphics.PathFillType.EvenOdd
import androidx.compose.ui.graphics.vector.VectorAsset
import androidx.compose.ui.graphics.vector.group
@@ -83,7 +85,7 @@
close()
}
group {
- materialPath {
+ materialPath(pathFillType = EvenOdd) {
moveTo(0.0f, 10.0f)
lineToRelative(-10.0f, 0.0f)
close()
@@ -100,6 +102,7 @@
private val path1 = VectorNode.Path(
strokeAlpha = 1f,
fillAlpha = 0.8f,
+ fillType = FillType.NonZero,
nodes = listOf(
PathNode.MoveTo(20f, 10f),
PathNode.RelativeLineTo(0f, 10f),
@@ -111,6 +114,7 @@
private val path2 = VectorNode.Path(
strokeAlpha = 1f,
fillAlpha = 1f,
+ fillType = FillType.EvenOdd,
nodes = listOf(
PathNode.MoveTo(0f, 10f),
PathNode.RelativeLineTo(-10f, 0f),
diff --git a/compose/material/material/integration-tests/material-demos/src/main/java/androidx/compose/material/demos/MaterialDemos.kt b/compose/material/material/integration-tests/material-demos/src/main/java/androidx/compose/material/demos/MaterialDemos.kt
index 3c2fb43..6bcaecd 100644
--- a/compose/material/material/integration-tests/material-demos/src/main/java/androidx/compose/material/demos/MaterialDemos.kt
+++ b/compose/material/material/integration-tests/material-demos/src/main/java/androidx/compose/material/demos/MaterialDemos.kt
@@ -16,16 +16,17 @@
package androidx.compose.material.demos
+import androidx.compose.integration.demos.common.ActivityDemo
+import androidx.compose.integration.demos.common.ComposableDemo
+import androidx.compose.integration.demos.common.DemoCategory
import androidx.compose.material.samples.AlertDialogSample
+import androidx.compose.material.samples.BackdropSample
import androidx.compose.material.samples.BottomDrawerSample
import androidx.compose.material.samples.CustomAlertDialogSample
import androidx.compose.material.samples.EmphasisSample
import androidx.compose.material.samples.ModalDrawerSample
import androidx.compose.material.samples.ScaffoldWithBottomBarAndCutout
import androidx.compose.material.samples.ScaffoldWithCoroutinesSnackbar
-import androidx.compose.integration.demos.common.ActivityDemo
-import androidx.compose.integration.demos.common.ComposableDemo
-import androidx.compose.integration.demos.common.DemoCategory
val MaterialDemos = DemoCategory("Material", listOf(
DemoCategory("AlertDialog", listOf(
@@ -33,6 +34,7 @@
ComposableDemo("Custom buttons") { CustomAlertDialogSample() }
)),
ComposableDemo("App Bars") { AppBarDemo() },
+ ComposableDemo("Backdrop") { BackdropSample() },
ComposableDemo("Bottom Navigation") { BottomNavigationDemo() },
ComposableDemo("Buttons & FABs") { ButtonDemo() },
DemoCategory("Navigation drawer", listOf(
diff --git a/compose/material/material/samples/src/main/java/androidx/compose/material/samples/BackdropScaffoldSamples.kt b/compose/material/material/samples/src/main/java/androidx/compose/material/samples/BackdropScaffoldSamples.kt
new file mode 100644
index 0000000..333cfc84
--- /dev/null
+++ b/compose/material/material/samples/src/main/java/androidx/compose/material/samples/BackdropScaffoldSamples.kt
@@ -0,0 +1,156 @@
+/*
+ * Copyright 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.compose.material.samples
+
+import androidx.annotation.Sampled
+import androidx.compose.foundation.Box
+import androidx.compose.foundation.ContentGravity
+import androidx.compose.foundation.Icon
+import androidx.compose.foundation.Text
+import androidx.compose.foundation.clickable
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.lazy.LazyColumnFor
+import androidx.compose.material.BackdropScaffold
+import androidx.compose.material.BackdropValue.Concealed
+import androidx.compose.material.ExperimentalMaterialApi
+import androidx.compose.material.IconButton
+import androidx.compose.material.ListItem
+import androidx.compose.material.TopAppBar
+import androidx.compose.material.icons.Icons
+import androidx.compose.material.icons.filled.Close
+import androidx.compose.material.icons.filled.Favorite
+import androidx.compose.material.icons.filled.Menu
+import androidx.compose.material.rememberBackdropState
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
+import androidx.compose.runtime.rememberCoroutineScope
+import androidx.compose.runtime.setValue
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.unit.dp
+import kotlinx.coroutines.launch
+
+@Sampled
+@Composable
+@OptIn(ExperimentalMaterialApi::class)
+fun BackdropSample() {
+ val selection = remember { mutableStateOf(1) }
+ val backdropScaffoldState = rememberBackdropState(Concealed)
+ BackdropScaffold(
+ backdropScaffoldState = backdropScaffoldState,
+ appBar = {
+ TopAppBar(
+ title = { Text("Backdrop") },
+ navigationIcon = {
+ if (backdropScaffoldState.isConcealed) {
+ IconButton(onClick = { backdropScaffoldState.reveal() }) {
+ Icon(Icons.Default.Menu)
+ }
+ } else {
+ IconButton(onClick = { backdropScaffoldState.conceal() }) {
+ Icon(Icons.Default.Close)
+ }
+ }
+ },
+ elevation = 0.dp,
+ backgroundColor = Color.Transparent
+ )
+ },
+ backLayerContent = {
+ LazyColumnFor((1..5).toList()) {
+ ListItem(
+ Modifier.clickable {
+ selection.value = it
+ backdropScaffoldState.conceal()
+ },
+ text = { Text("Select $it") }
+ )
+ }
+ },
+ frontLayerContent = {
+ Box(
+ Modifier.fillMaxSize(),
+ gravity = ContentGravity.Center
+ ) {
+ Text("Selection: ${selection.value}")
+ }
+ }
+ )
+}
+
+@Sampled
+@Composable
+@OptIn(ExperimentalMaterialApi::class)
+fun BackdropWithSnackbarSample() {
+ val scope = rememberCoroutineScope()
+ val selection = remember { mutableStateOf(1) }
+ val backdropScaffoldState = rememberBackdropState(Concealed)
+ BackdropScaffold(
+ backdropScaffoldState = backdropScaffoldState,
+ appBar = {
+ TopAppBar(
+ title = { Text("Backdrop") },
+ navigationIcon = {
+ if (backdropScaffoldState.isConcealed) {
+ IconButton(onClick = { backdropScaffoldState.reveal() }) {
+ Icon(Icons.Default.Menu)
+ }
+ } else {
+ IconButton(onClick = { backdropScaffoldState.conceal() }) {
+ Icon(Icons.Default.Close)
+ }
+ }
+ },
+ actions = {
+ var clickCount by remember { mutableStateOf(0) }
+ IconButton(onClick = {
+ // show snackbar as a suspend function
+ scope.launch {
+ backdropScaffoldState.snackbarHostState
+ .showSnackbar("Snackbar #${++clickCount}")
+ }
+ }) {
+ Icon(Icons.Default.Favorite)
+ }
+ },
+ elevation = 0.dp,
+ backgroundColor = Color.Transparent
+ )
+ },
+ backLayerContent = {
+ LazyColumnFor((1..5).toList()) {
+ ListItem(
+ Modifier.clickable {
+ selection.value = it
+ backdropScaffoldState.conceal()
+ },
+ text = { Text("Select $it") }
+ )
+ }
+ },
+ frontLayerContent = {
+ Box(
+ Modifier.fillMaxSize(),
+ gravity = ContentGravity.Center
+ ) {
+ Text("Selection: ${selection.value}")
+ }
+ }
+ )
+}
diff --git a/compose/material/material/samples/src/main/java/androidx/compose/material/samples/TextFieldSamples.kt b/compose/material/material/samples/src/main/java/androidx/compose/material/samples/TextFieldSamples.kt
index 0fb888c..b7a0ec0 100644
--- a/compose/material/material/samples/src/main/java/androidx/compose/material/samples/TextFieldSamples.kt
+++ b/compose/material/material/samples/src/main/java/androidx/compose/material/samples/TextFieldSamples.kt
@@ -70,7 +70,7 @@
TextField(value = text,
onValueChange = { text = it },
- label = { Text("Label") },
+ placeholder = { Text("placeholder") },
leadingIcon = { Icon(Icons.Filled.Favorite) },
trailingIcon = { Icon(Icons.Filled.Info) }
)
diff --git a/compose/material/material/src/androidAndroidTest/kotlin/androidx/compose/material/BackdropScaffoldTest.kt b/compose/material/material/src/androidAndroidTest/kotlin/androidx/compose/material/BackdropScaffoldTest.kt
new file mode 100644
index 0000000..9e27990
--- /dev/null
+++ b/compose/material/material/src/androidAndroidTest/kotlin/androidx/compose/material/BackdropScaffoldTest.kt
@@ -0,0 +1,247 @@
+/*
+ * Copyright 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.compose.material
+
+import androidx.compose.animation.core.ManualAnimationClock
+import androidx.compose.foundation.Box
+import androidx.compose.foundation.layout.fillMaxHeight
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.layout.preferredHeight
+import androidx.compose.material.BackdropValue.Concealed
+import androidx.compose.material.BackdropValue.Revealed
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.platform.testTag
+import androidx.compose.ui.unit.dp
+import androidx.test.filters.MediumTest
+import androidx.ui.test.assertTopPositionInRootIsEqualTo
+import androidx.ui.test.click
+import androidx.ui.test.createComposeRule
+import androidx.ui.test.onNodeWithTag
+import androidx.ui.test.performGesture
+import androidx.ui.test.runOnIdle
+import androidx.ui.test.swipeDown
+import com.google.common.truth.Truth.assertThat
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+
+@MediumTest
+@RunWith(JUnit4::class)
+@OptIn(ExperimentalMaterialApi::class)
+class BackdropScaffoldTest {
+
+ @get:Rule
+ val composeTestRule = createComposeRule(disableTransitions = true)
+
+ private val peekHeight = 75.dp
+ private val headerHeight = 100.dp
+ private val contentHeight = 150.dp
+
+ private val frontLayer = "frontLayerTag"
+
+ private lateinit var clock: ManualAnimationClock
+
+ private fun advanceClock() {
+ clock.clockTimeMillis += 100000L
+ }
+
+ @Before
+ fun init() {
+ clock = ManualAnimationClock(initTimeMillis = 0L)
+ }
+
+ @Test
+ fun backdropScaffold_testOffset_whenConcealed() {
+ composeTestRule.setContent {
+ BackdropScaffold(
+ backdropScaffoldState = rememberBackdropState(Concealed),
+ peekHeight = peekHeight,
+ headerHeight = headerHeight,
+ appBar = { Box(Modifier.preferredHeight(peekHeight)) },
+ backLayerContent = { Box(Modifier.preferredHeight(contentHeight)) },
+ frontLayerContent = { Box(Modifier.fillMaxSize().testTag(frontLayer)) }
+ )
+ }
+
+ onNodeWithTag(frontLayer)
+ .assertTopPositionInRootIsEqualTo(peekHeight)
+ }
+
+ @Test
+ fun backdropScaffold_testOffset_whenRevealed() {
+ composeTestRule.setContent {
+ BackdropScaffold(
+ backdropScaffoldState = rememberBackdropState(Revealed),
+ peekHeight = peekHeight,
+ headerHeight = headerHeight,
+ appBar = { Box(Modifier.preferredHeight(peekHeight)) },
+ backLayerContent = { Box(Modifier.preferredHeight(contentHeight)) },
+ frontLayerContent = { Box(Modifier.fillMaxSize().testTag(frontLayer)) }
+ )
+ }
+
+ onNodeWithTag(frontLayer)
+ .assertTopPositionInRootIsEqualTo(peekHeight + contentHeight)
+ }
+
+ @Test
+ fun backdropScaffold_testOffset_whenRevealed_backContentTooLarge() {
+ composeTestRule.setContent {
+ BackdropScaffold(
+ backdropScaffoldState = rememberBackdropState(Revealed),
+ peekHeight = peekHeight,
+ headerHeight = headerHeight,
+ appBar = { Box(Modifier.preferredHeight(peekHeight)) },
+ backLayerContent = { Box(Modifier.fillMaxHeight()) },
+ frontLayerContent = { Box(Modifier.fillMaxSize().testTag(frontLayer)) }
+ )
+ }
+
+ onNodeWithTag(frontLayer)
+ .assertTopPositionInRootIsEqualTo(rootHeight() - headerHeight)
+ }
+
+ @Test
+ fun backdropScaffold_testOffset_whenRevealed_nonPersistentAppBar() {
+ composeTestRule.setContent {
+ BackdropScaffold(
+ backdropScaffoldState = rememberBackdropState(Revealed),
+ peekHeight = peekHeight,
+ headerHeight = headerHeight,
+ persistentAppBar = false,
+ appBar = { Box(Modifier.preferredHeight(peekHeight)) },
+ backLayerContent = { Box(Modifier.preferredHeight(contentHeight)) },
+ frontLayerContent = { Box(Modifier.fillMaxSize().testTag(frontLayer)) }
+ )
+ }
+
+ onNodeWithTag(frontLayer)
+ .assertTopPositionInRootIsEqualTo(contentHeight)
+ }
+
+ @Test
+ fun backdropScaffold_testOffset_whenRevealed_nonStickyFrontLayer() {
+ composeTestRule.setContent {
+ BackdropScaffold(
+ backdropScaffoldState = rememberBackdropState(Revealed),
+ peekHeight = peekHeight,
+ headerHeight = headerHeight,
+ stickyFrontLayer = false,
+ appBar = { Box(Modifier.preferredHeight(peekHeight)) },
+ backLayerContent = { Box(Modifier.preferredHeight(contentHeight)) },
+ frontLayerContent = { Box(Modifier.fillMaxSize().testTag(frontLayer)) }
+ )
+ }
+
+ onNodeWithTag(frontLayer)
+ .assertTopPositionInRootIsEqualTo(rootHeight() - headerHeight)
+ }
+
+ @Test
+ fun backdropScaffold_revealAndConceal_manually() {
+ val backdropState = BackdropScaffoldState(Concealed, clock = clock)
+ composeTestRule.setContent {
+ BackdropScaffold(
+ backdropScaffoldState = backdropState,
+ peekHeight = peekHeight,
+ headerHeight = headerHeight,
+ appBar = { Box(Modifier.preferredHeight(peekHeight)) },
+ backLayerContent = { Box(Modifier.preferredHeight(contentHeight)) },
+ frontLayerContent = { Box(Modifier.fillMaxSize().testTag(frontLayer)) }
+ )
+ }
+
+ onNodeWithTag(frontLayer)
+ .assertTopPositionInRootIsEqualTo(peekHeight)
+
+ runOnIdle {
+ backdropState.reveal()
+ }
+
+ advanceClock()
+
+ onNodeWithTag(frontLayer)
+ .assertTopPositionInRootIsEqualTo(peekHeight + contentHeight)
+
+ runOnIdle {
+ backdropState.conceal()
+ }
+
+ advanceClock()
+
+ onNodeWithTag(frontLayer)
+ .assertTopPositionInRootIsEqualTo(peekHeight)
+ }
+
+ @Test
+ fun backdropScaffold_revealBySwiping() {
+ val backdropState = BackdropScaffoldState(Concealed, clock)
+ composeTestRule.setContent {
+ BackdropScaffold(
+ backdropScaffoldState = backdropState,
+ peekHeight = peekHeight,
+ headerHeight = headerHeight,
+ appBar = { Box(Modifier.preferredHeight(peekHeight)) },
+ backLayerContent = { Box(Modifier.preferredHeight(contentHeight)) },
+ frontLayerContent = { Box(Modifier.fillMaxSize().testTag(frontLayer)) }
+ )
+ }
+
+ runOnIdle {
+ assertThat(backdropState.value).isEqualTo(Concealed)
+ }
+
+ onNodeWithTag(frontLayer)
+ .performGesture { swipeDown() }
+
+ advanceClock()
+
+ runOnIdle {
+ assertThat(backdropState.value).isEqualTo(Revealed)
+ }
+ }
+
+ @Test
+ fun backdropScaffold_concealByTapingOnFrontLayer() {
+ val backdropState = BackdropScaffoldState(Revealed, clock)
+ composeTestRule.setContent {
+ BackdropScaffold(
+ backdropScaffoldState = backdropState,
+ peekHeight = peekHeight,
+ headerHeight = headerHeight,
+ appBar = { Box(Modifier.preferredHeight(peekHeight)) },
+ backLayerContent = { Box(Modifier.preferredHeight(contentHeight)) },
+ frontLayerContent = { Box(Modifier.fillMaxSize().testTag(frontLayer)) }
+ )
+ }
+
+ runOnIdle {
+ assertThat(backdropState.value).isEqualTo(Revealed)
+ }
+
+ onNodeWithTag(frontLayer)
+ .performGesture { click() }
+
+ advanceClock()
+
+ runOnIdle {
+ assertThat(backdropState.value).isEqualTo(Concealed)
+ }
+ }
+}
\ No newline at end of file
diff --git a/compose/material/material/src/androidAndroidTest/kotlin/androidx/compose/material/textfield/TextFieldTest.kt b/compose/material/material/src/androidAndroidTest/kotlin/androidx/compose/material/textfield/TextFieldTest.kt
index 6ce3735..c29ef0d 100644
--- a/compose/material/material/src/androidAndroidTest/kotlin/androidx/compose/material/textfield/TextFieldTest.kt
+++ b/compose/material/material/src/androidAndroidTest/kotlin/androidx/compose/material/textfield/TextFieldTest.kt
@@ -70,7 +70,6 @@
import androidx.ui.test.performGesture
import androidx.ui.test.performImeAction
import androidx.ui.test.runOnIdle
-import androidx.ui.test.waitForIdle
import com.google.common.truth.Truth.assertThat
import com.nhaarman.mockitokotlin2.any
import com.nhaarman.mockitokotlin2.atLeastOnce
@@ -284,7 +283,7 @@
}
// click to focus
- clickAndAdvanceClock(TextfieldTag, 200)
+ clickAndAdvanceClock()
testRule.runOnIdleWithDensity {
// size
@@ -361,7 +360,7 @@
}
}
// click to focus
- clickAndAdvanceClock(TextfieldTag, 200)
+ clickAndAdvanceClock()
testRule.runOnIdleWithDensity {
// size
@@ -402,7 +401,7 @@
}
}
// click to focus
- clickAndAdvanceClock(TextfieldTag, 200)
+ clickAndAdvanceClock()
testRule.runOnIdleWithDensity {
// size
@@ -443,7 +442,7 @@
}
// click to focus
- clickAndAdvanceClock(TextfieldTag, 200)
+ clickAndAdvanceClock()
testRule.runOnIdleWithDensity {
assertThat(placeholderSize.value).isNull()
@@ -657,7 +656,7 @@
}
}
- clickAndAdvanceClock(TextfieldTag, 200)
+ clickAndAdvanceClock()
runOnIdle {
verify(textInputService, atLeastOnce()).startInput(
@@ -798,10 +797,8 @@
}
}
- private fun clickAndAdvanceClock(tag: String, time: Long) {
- onNodeWithTag(tag).performClick()
- waitForIdle()
- testRule.clockTestRule.pauseClock()
- testRule.clockTestRule.advanceClock(time)
+ private fun clickAndAdvanceClock() {
+ onNodeWithTag(TextfieldTag).performClick()
+ testRule.clockTestRule.advanceClock(200L)
}
}
\ No newline at end of file
diff --git a/compose/material/material/src/commonMain/kotlin/androidx/compose/material/BackdropScaffold.kt b/compose/material/material/src/commonMain/kotlin/androidx/compose/material/BackdropScaffold.kt
new file mode 100644
index 0000000..cc778c8
--- /dev/null
+++ b/compose/material/material/src/commonMain/kotlin/androidx/compose/material/BackdropScaffold.kt
@@ -0,0 +1,485 @@
+/*
+ * Copyright 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.compose.material
+
+import androidx.compose.animation.animate
+import androidx.compose.animation.asDisposableClock
+import androidx.compose.animation.core.AnimationClockObservable
+import androidx.compose.animation.core.AnimationEndReason.Interrupted
+import androidx.compose.animation.core.AnimationSpec
+import androidx.compose.animation.core.TweenSpec
+import androidx.compose.foundation.Box
+import androidx.compose.foundation.Canvas
+import androidx.compose.foundation.ContentGravity
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.Stack
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.layout.offsetPx
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.shape.CornerSize
+import androidx.compose.material.BackdropValue.Concealed
+import androidx.compose.material.BackdropValue.Revealed
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.remember
+import androidx.compose.runtime.savedinstancestate.Saver
+import androidx.compose.runtime.savedinstancestate.rememberSavedInstanceState
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.drawLayer
+import androidx.compose.ui.gesture.scrollorientationlocking.Orientation
+import androidx.compose.ui.gesture.tapGestureFilter
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.graphics.Shape
+import androidx.compose.ui.layout.ExperimentalSubcomposeLayoutApi
+import androidx.compose.ui.layout.SubcomposeLayout
+import androidx.compose.ui.platform.AnimationClockAmbient
+import androidx.compose.ui.platform.DensityAmbient
+import androidx.compose.ui.unit.Constraints
+import androidx.compose.ui.unit.Dp
+import androidx.compose.ui.unit.dp
+import androidx.compose.ui.unit.offset
+import androidx.compose.ui.util.fastForEach
+import androidx.compose.ui.util.fastMap
+import androidx.compose.ui.zIndex
+import kotlin.math.max
+import kotlin.math.min
+import kotlin.math.roundToInt
+
+/**
+ * Possible values of [BackdropScaffoldState].
+ */
+enum class BackdropValue {
+ /**
+ * Indicates the back layer is concealed and the front layer is active.
+ */
+ Concealed,
+
+ /**
+ * Indicates the back layer is revealed and the front layer is inactive.
+ */
+ Revealed
+}
+
+/**
+ * State of the [BackdropScaffold] composable.
+ *
+ * @param initialValue The initial value of the state.
+ * @param clock The animation clock that will be used to drive the animations.
+ * @param animationSpec The default animation that will be used to animate to a new state.
+ * @param confirmStateChange Optional callback invoked to confirm or veto a pending state change.
+ * @param snackbarHostState The [SnackbarHostState] used to show snackbars inside the backdrop.
+ */
+@ExperimentalMaterialApi
+class BackdropScaffoldState(
+ initialValue: BackdropValue,
+ clock: AnimationClockObservable,
+ animationSpec: AnimationSpec<Float> = SwipeableConstants.DefaultAnimationSpec,
+ confirmStateChange: (BackdropValue) -> Boolean = { true },
+ val snackbarHostState: SnackbarHostState = SnackbarHostState()
+) : SwipeableState<BackdropValue>(
+ initialValue = initialValue,
+ clock = clock,
+ animationSpec = animationSpec,
+ confirmStateChange = confirmStateChange
+) {
+ /**
+ * Whether the back layer is revealed.
+ */
+ val isRevealed: Boolean
+ get() = value == Revealed
+
+ /**
+ * Whether the back layer is concealed.
+ */
+ val isConcealed: Boolean
+ get() = value == Concealed
+
+ /**
+ * Reveal the back layer, with an animation.
+ *
+ * @param onRevealed Optional callback invoked when the back layer has been revealed.
+ */
+ fun reveal(onRevealed: (() -> Unit)? = null) {
+ animateTo(targetValue = Revealed, onEnd = { endReason, endValue ->
+ if (endReason != Interrupted && endValue == Revealed) {
+ onRevealed?.invoke()
+ }
+ })
+ }
+
+ /**
+ * Conceal the back layer, with an animation.
+ *
+ * @param onConcealed Optional callback invoked when the back layer has been concealed.
+ */
+ fun conceal(onConcealed: (() -> Unit)? = null) {
+ animateTo(targetValue = Concealed, onEnd = { endReason, endValue ->
+ if (endReason != Interrupted && endValue == Concealed) {
+ onConcealed?.invoke()
+ }
+ })
+ }
+
+ companion object {
+ /**
+ * The default [Saver] implementation for [BackdropScaffoldState].
+ */
+ fun Saver(
+ clock: AnimationClockObservable,
+ animationSpec: AnimationSpec<Float>,
+ confirmStateChange: (BackdropValue) -> Boolean,
+ snackbarHostState: SnackbarHostState
+ ): Saver<BackdropScaffoldState, *> = Saver(
+ save = { it.value },
+ restore = {
+ BackdropScaffoldState(
+ initialValue = it,
+ clock = clock,
+ animationSpec = animationSpec,
+ confirmStateChange = confirmStateChange,
+ snackbarHostState = snackbarHostState
+ )
+ }
+ )
+ }
+}
+
+/**
+ * Create and [remember] a [BackdropScaffoldState] with the default animation clock.
+ *
+ * @param initialValue The initial value of the state.
+ * @param animationSpec The default animation that will be used to animate to a new state.
+ * @param confirmStateChange Optional callback invoked to confirm or veto a pending state change.
+ * @param snackbarHostState The [SnackbarHostState] used to show snackbars inside the backdrop.
+ */
+@Composable
+@ExperimentalMaterialApi
+fun rememberBackdropState(
+ initialValue: BackdropValue,
+ animationSpec: AnimationSpec<Float> = SwipeableConstants.DefaultAnimationSpec,
+ confirmStateChange: (BackdropValue) -> Boolean = { true },
+ snackbarHostState: SnackbarHostState = SnackbarHostState()
+): BackdropScaffoldState {
+ val clock = AnimationClockAmbient.current.asDisposableClock()
+ return rememberSavedInstanceState(
+ clock,
+ saver = BackdropScaffoldState.Saver(
+ clock = clock,
+ animationSpec = animationSpec,
+ confirmStateChange = confirmStateChange,
+ snackbarHostState = snackbarHostState
+ )
+ ) {
+ BackdropScaffoldState(
+ initialValue = initialValue,
+ clock = clock,
+ animationSpec = animationSpec,
+ confirmStateChange = confirmStateChange,
+ snackbarHostState = snackbarHostState
+ )
+ }
+}
+
+/**
+ * A backdrop appears behind all other surfaces in an app, displaying contextual and actionable
+ * content. It is composed of two surfaces: a back layer and a front layer. The back layer
+ * displays actions and context, and these control and inform the front layer's content.
+ *
+ * This component provides an API to put together several material components to construct your
+ * screen. For a similar component which implements the basic material design layout strategy
+ * with app bars, floating action buttons and navigation drawers, use the standard [Scaffold].
+ *
+ * Either the back layer or front layer can be active at a time. When the front layer is active,
+ * it sits at an offset below the top of the screen. This is the [peekHeight] and defaults to
+ * 56dp which is the default app bar height. When the front layer is inactive, it sticks to the
+ * height of the back layer's content if [stickyFrontLayer] is set to `true` and the height of
+ * the front layer exceeds the [headerHeight], and otherwise it minimizes to the [headerHeight].
+ * To switch between the back layer and front layer, you can either swipe on the front layer if
+ * [gesturesEnabled] is set to `true` or use any of the methods in [BackdropScaffoldState].
+ *
+ * The backdrop also contains an app bar, which by default is placed above the back layer's
+ * content. If [persistentAppBar] is set to `false`, then the backdrop will not show the app bar
+ * when the back layer is revealed; instead it will switch between the app bar and the provided
+ * content with an animation. For best results, the [peekHeight] should match the app bar height.
+ *
+ * A simple example of a backdrop looks like this:
+ *
+ * @sample androidx.compose.material.samples.BackdropSample
+ *
+ * To show a snackbar, use [BackdropScaffoldState.snackbarHostState]:
+ *
+ * @sample androidx.compose.material.samples.BackdropWithSnackbarSample
+ *
+ * @param modifier Optional [Modifier] for the entire backdrop.
+ * @param backdropScaffoldState The state of the backdrop.
+ * @param gesturesEnabled Whether or not the backdrop can be interacted with by gestures.
+ * @param peekHeight The height of the visible part of the back layer when it is concealed.
+ * @param headerHeight The minimum height of the front layer when it is inactive.
+ * @param persistentAppBar Whether the app bar should be shown when the back layer is revealed.
+ * By default, it will always be shown above the back layer's content. If this is set to `false`,
+ * the back layer will automatically switch between the app bar and its content with an animation.
+ * @param stickyFrontLayer Whether the front layer should stick to the height of the back layer.
+ * @param backLayerBackgroundColor The background color of the back layer.
+ * @param backLayerContentColor The preferred content color provided by the back layer to its
+ * children. Defaults to the matching `onFoo` color for [backLayerBackgroundColor], or if that
+ * is not a color from the theme, this will keep the same content color set above the back layer.
+ * @param frontLayerShape The shape of the front layer.
+ * @param frontLayerElevation The elevation of the front layer.
+ * @param frontLayerBackgroundColor The background color of the front layer.
+ * @param frontLayerContentColor The preferred content color provided by the back front to its
+ * children. Defaults to the matching `onFoo` color for [frontLayerBackgroundColor], or if that
+ * is not a color from the theme, this will keep the same content color set above the front layer.
+ * @param frontLayerScrimColor The color of the scrim applied to the front layer when inactive.
+ * @param snackbarHost The component hosting the snackbars shown inside the backdrop.
+ * @param appBar App bar for the back layer. Make sure that the [peekHeight] is equal to the
+ * height of the app bar, so that the app bar is fully visible. Consider using [TopAppBar] but
+ * set the elevation to 0dp and background color to transparent as a surface is already provided.
+ * @param backLayerContent The content of the back layer.
+ * @param frontLayerContent The content of the front layer.
+ */
+@Composable
+@ExperimentalMaterialApi
+fun BackdropScaffold(
+ modifier: Modifier = Modifier,
+ backdropScaffoldState: BackdropScaffoldState = rememberBackdropState(Concealed),
+ gesturesEnabled: Boolean = true,
+ peekHeight: Dp = BackdropConstants.DefaultPeekHeight,
+ headerHeight: Dp = BackdropConstants.DefaultHeaderHeight,
+ persistentAppBar: Boolean = true,
+ stickyFrontLayer: Boolean = true,
+ backLayerBackgroundColor: Color = MaterialTheme.colors.primary,
+ backLayerContentColor: Color = contentColorFor(backLayerBackgroundColor),
+ frontLayerShape: Shape = BackdropConstants.DefaultFrontLayerShape,
+ frontLayerElevation: Dp = BackdropConstants.DefaultFrontLayerElevation,
+ frontLayerBackgroundColor: Color = MaterialTheme.colors.surface,
+ frontLayerContentColor: Color = contentColorFor(frontLayerBackgroundColor),
+ frontLayerScrimColor: Color = BackdropConstants.DefaultFrontLayerScrimColor,
+ snackbarHost: @Composable (SnackbarHostState) -> Unit = { SnackbarHost(it) },
+ appBar: @Composable () -> Unit,
+ backLayerContent: @Composable () -> Unit,
+ frontLayerContent: @Composable () -> Unit
+) {
+ val peekHeightPx = with(DensityAmbient.current) { peekHeight.toPx() }
+ val headerHeightPx = with(DensityAmbient.current) { headerHeight.toPx() }
+
+ val backLayer = @Composable {
+ if (persistentAppBar) {
+ Column {
+ appBar()
+ backLayerContent()
+ }
+ } else {
+ BackLayerTransition(backdropScaffoldState.targetValue, appBar, backLayerContent)
+ }
+ }
+ val calculateBackLayerConstraints: (Constraints) -> Constraints = {
+ it.copy(minWidth = 0, minHeight = 0).offset(vertical = -headerHeightPx.roundToInt())
+ }
+
+ // Back layer
+ Surface(
+ color = backLayerBackgroundColor,
+ contentColor = backLayerContentColor
+ ) {
+ WithBackLayerHeight(
+ modifier.fillMaxSize(),
+ backLayer,
+ calculateBackLayerConstraints
+ ) { constraints, backLayerHeight ->
+ val fullHeight = constraints.maxHeight.toFloat()
+ var revealedHeight = fullHeight - headerHeightPx
+ if (stickyFrontLayer) {
+ revealedHeight = min(revealedHeight, backLayerHeight)
+ }
+
+ val swipeable = Modifier.swipeable(
+ state = backdropScaffoldState,
+ anchors = mapOf(
+ peekHeightPx to Concealed,
+ revealedHeight to Revealed
+ ),
+ thresholds = { _, _ -> FixedThreshold(56.dp) },
+ orientation = Orientation.Vertical,
+ enabled = gesturesEnabled,
+ resistanceFactorAtMin = SwipeableConstants.StiffResistanceFactor,
+ resistanceFactorAtMax = SwipeableConstants.StiffResistanceFactor
+ )
+
+ // Front layer
+ Surface(
+ Modifier.offsetPx(y = backdropScaffoldState.offset).then(swipeable),
+ shape = frontLayerShape,
+ elevation = frontLayerElevation,
+ color = frontLayerBackgroundColor,
+ contentColor = frontLayerContentColor
+ ) {
+ Stack(Modifier.padding(bottom = peekHeight)) {
+ frontLayerContent()
+
+ Scrim(
+ color = frontLayerScrimColor,
+ onDismiss = { backdropScaffoldState.conceal() },
+ visible = backdropScaffoldState.targetValue == Revealed
+ )
+ }
+ }
+
+ // Snackbar host
+ Box(
+ Modifier.zIndex(Float.POSITIVE_INFINITY),
+ gravity = ContentGravity.BottomCenter,
+ paddingBottom = if (backdropScaffoldState.isRevealed &&
+ revealedHeight == fullHeight - headerHeightPx) headerHeight else 0.dp
+ ) {
+ snackbarHost(backdropScaffoldState.snackbarHostState)
+ }
+ }
+ }
+}
+
+@Composable
+private fun Scrim(
+ color: Color,
+ onDismiss: () -> Unit,
+ visible: Boolean
+) {
+ val alpha = animate(target = if (visible) 1f else 0f, animSpec = TweenSpec())
+ val dismissModifier = if (visible) Modifier.tapGestureFilter { onDismiss() } else Modifier
+
+ Canvas(
+ Modifier
+ .fillMaxSize()
+ .then(dismissModifier)
+ ) {
+ drawRect(color = color, alpha = alpha)
+ }
+}
+
+/**
+ * A shared axis transition, used in the back layer. Both the [appBar] and the [content] shift
+ * vertically, while they crossfade. It is very important that both are composed and measured,
+ * even if invisible, and that this component is as large as both of them.
+ */
+@Composable
+private fun BackLayerTransition(
+ target: BackdropValue,
+ appBar: @Composable () -> Unit,
+ content: @Composable () -> Unit
+) {
+ // The progress of the animation between Revealed (0) and Concealed (2).
+ // The midpoint (1) is the point where the appBar and backContent are switched.
+ val animationProgress = animate(
+ target = if (target == Revealed) 0f else 2f, animSpec = TweenSpec()
+ )
+ val animationSlideOffset = with(DensityAmbient.current) { AnimationSlideOffset.toPx() }
+
+ val appBarFloat = (animationProgress - 1).coerceIn(0f, 1f)
+ val contentFloat = (1 - animationProgress).coerceIn(0f, 1f)
+
+ Stack {
+ Box(
+ Modifier.zIndex(appBarFloat).drawLayer(
+ alpha = appBarFloat,
+ translationY = (1 - appBarFloat) * animationSlideOffset
+ ),
+ children = appBar
+ )
+ Box(
+ Modifier.zIndex(contentFloat).drawLayer(
+ alpha = contentFloat,
+ translationY = (1 - contentFloat) * -animationSlideOffset
+ ),
+ children = content
+ )
+ }
+}
+
+/**
+ * A composable that defines its own content according to the height of one of its children.
+ */
+@Composable
+@OptIn(ExperimentalSubcomposeLayoutApi::class)
+private fun WithBackLayerHeight(
+ modifier: Modifier,
+ backLayer: @Composable () -> Unit,
+ calculateBackLayerConstraints: (Constraints) -> Constraints,
+ frontLayer: @Composable (Constraints, Float) -> Unit
+) {
+ SubcomposeLayout<BackdropLayers>(modifier) { constraints ->
+ val backLayerPlaceable =
+ subcompose(BackdropLayers.Back, backLayer).first()
+ .measure(calculateBackLayerConstraints(constraints))
+
+ val backLayerHeight = backLayerPlaceable.height.toFloat()
+
+ val placeables =
+ subcompose(BackdropLayers.Front) {
+ frontLayer(constraints, backLayerHeight)
+ }.fastMap { it.measure(constraints) }
+
+ var maxWidth = max(constraints.minWidth, backLayerPlaceable.width)
+ var maxHeight = max(constraints.minHeight, backLayerPlaceable.height)
+ placeables.fastForEach {
+ maxWidth = max(maxWidth, it.width)
+ maxHeight = max(maxHeight, it.height)
+ }
+
+ layout(maxWidth, maxHeight) {
+ backLayerPlaceable.placeRelative(0, 0)
+ placeables.fastForEach { it.placeRelative(0, 0) }
+ }
+ }
+}
+
+private enum class BackdropLayers { Back, Front }
+
+/**
+ * Contains useful constants for [BackdropScaffold].
+ */
+object BackdropConstants {
+
+ /**
+ * The default peek height of the back layer.
+ */
+ val DefaultPeekHeight = 56.dp
+
+ /**
+ * The default header height of the front layer.
+ */
+ val DefaultHeaderHeight = 48.dp
+
+ /**
+ * The default shape of the front layer.
+ */
+ @Composable
+ val DefaultFrontLayerShape: Shape
+ get() = MaterialTheme.shapes.large
+ .copy(topLeft = CornerSize(16.dp), topRight = CornerSize(16.dp))
+
+ /**
+ * The default elevation of the front layer.
+ */
+ val DefaultFrontLayerElevation = 1.dp
+
+ /**
+ * The default color of the scrim applied to the front layer.
+ */
+ @Composable
+ val DefaultFrontLayerScrimColor: Color
+ get() = MaterialTheme.colors.surface.copy(alpha = 0.60f)
+}
+
+private val AnimationSlideOffset = 20.dp
\ No newline at end of file
diff --git a/compose/material/material/src/commonMain/kotlin/androidx/compose/material/OutlinedTextField.kt b/compose/material/material/src/commonMain/kotlin/androidx/compose/material/OutlinedTextField.kt
index 7439a94..e609011 100644
--- a/compose/material/material/src/commonMain/kotlin/androidx/compose/material/OutlinedTextField.kt
+++ b/compose/material/material/src/commonMain/kotlin/androidx/compose/material/OutlinedTextField.kt
@@ -69,12 +69,12 @@
* @param value the input text to be shown in the text field
* @param onValueChange the callback that is triggered when the input service updates the text. An
* updated text comes as a parameter of the callback
- * @param label the label to be displayed inside the text field container. The default text style
- * for internal [Text] is [Typography.caption] when the text field is in focus and
- * [Typography.subtitle1] when the text field is not in focus
* @param modifier a [Modifier] for this text field
* @param textStyle the style to be applied to the input text. The default [textStyle] uses the
* [currentTextStyle] defined by the theme
+ * @param label the optional label to be displayed inside the text field container. The default
+ * text style for internal [Text] is [Typography.caption] when the text field is in focus and
+ * [Typography.subtitle1] when the text field is not in focus
* @param placeholder the optional placeholder to be displayed when the text field is in focus and
* the input text is empty. The default text style for internal [Text] is [Typography.subtitle1]
* @param leadingIcon the optional leading icon to be displayed at the beginning of the text field
@@ -112,9 +112,9 @@
fun OutlinedTextField(
value: String,
onValueChange: (String) -> Unit,
- label: @Composable () -> Unit,
modifier: Modifier = Modifier,
textStyle: TextStyle = currentTextStyle(),
+ label: @Composable (() -> Unit)? = null,
placeholder: @Composable (() -> Unit)? = null,
leadingIcon: @Composable (() -> Unit)? = null,
trailingIcon: @Composable (() -> Unit)? = null,
@@ -130,6 +130,7 @@
) {
var selection by remember { mutableStateOf(TextRange.Zero) }
var composition by remember { mutableStateOf<TextRange?>(null) }
+
@OptIn(InternalTextApi::class)
val textFieldValue = TextFieldValue(
text = value,
@@ -181,12 +182,12 @@
* @param value the input [TextFieldValue] to be shown in the text field
* @param onValueChange the callback that is triggered when the input service updates values in
* [TextFieldValue]. An updated [TextFieldValue] comes as a parameter of the callback
- * @param label the label to be displayed inside the text field container. The default text style
- * for internal [Text] is [Typography.caption] when the text field is in focus and
- * [Typography.subtitle1] when the text field is not in focus
* @param modifier a [Modifier] for this text field
* @param textStyle the style to be applied to the input text. The default [textStyle] uses the
* [currentTextStyle] defined by the theme
+ * @param label the optional label to be displayed inside the text field container. The default
+ * text style for internal [Text] is [Typography.caption] when the text field is in focus and
+ * [Typography.subtitle1] when the text field is not in focus
* @param placeholder the optional placeholder to be displayed when the text field is in focus and
* the input text is empty. The default text style for internal [Text] is [Typography.subtitle1]
* @param leadingIcon the optional leading icon to be displayed at the beginning of the text field
@@ -224,9 +225,9 @@
fun OutlinedTextField(
value: TextFieldValue,
onValueChange: (TextFieldValue) -> Unit,
- label: @Composable () -> Unit,
modifier: Modifier = Modifier,
textStyle: TextStyle = currentTextStyle(),
+ label: @Composable (() -> Unit)? = null,
placeholder: @Composable (() -> Unit)? = null,
leadingIcon: @Composable (() -> Unit)? = null,
trailingIcon: @Composable (() -> Unit)? = null,
@@ -269,8 +270,8 @@
internal fun OutlinedTextFieldLayout(
modifier: Modifier = Modifier,
decoratedTextField: @Composable (Modifier) -> Unit,
- decoratedPlaceholder: @Composable (() -> Unit)?,
- decoratedLabel: @Composable () -> Unit,
+ decoratedPlaceholder: @Composable ((Modifier) -> Unit)?,
+ decoratedLabel: @Composable (() -> Unit)?,
leading: @Composable (() -> Unit)?,
trailing: @Composable (() -> Unit)?,
leadingColor: Color,
@@ -322,8 +323,8 @@
private fun IconsWithTextFieldLayout(
modifier: Modifier = Modifier,
textField: @Composable (Modifier) -> Unit,
- placeholder: @Composable (() -> Unit)?,
- label: @Composable () -> Unit,
+ placeholder: @Composable ((Modifier) -> Unit)?,
+ label: @Composable (() -> Unit)?,
leading: @Composable (() -> Unit)?,
trailing: @Composable (() -> Unit)?,
leadingColor: Color,
@@ -350,12 +351,7 @@
}
}
if (placeholder != null) {
- Box(
- modifier = Modifier
- .layoutId(PlaceholderId)
- .padding(horizontal = TextFieldPadding),
- children = placeholder
- )
+ placeholder(Modifier.layoutId(PlaceholderId).padding(horizontal = TextFieldPadding))
}
textField(
@@ -364,7 +360,9 @@
.padding(horizontal = TextFieldPadding)
)
- Box(modifier = Modifier.layoutId(LabelId), children = label)
+ if (label != null) {
+ Box(modifier = Modifier.layoutId(LabelId), children = label)
+ }
},
modifier = modifier
) { measurables, incomingConstraints ->
@@ -393,13 +391,13 @@
vertical = -bottomPadding
)
val labelPlaceable =
- measurables.first { it.id == LabelId }.measure(labelConstraints)
- onLabelMeasured(labelPlaceable.width)
+ measurables.find { it.id == LabelId }?.measure(labelConstraints)
+ onLabelMeasured(labelPlaceable?.width ?: 0)
// measure text field
// on top we offset either by default padding or by label's half height if its too big
// minWidth must not be set to 0 due to how foundation TextField treats zero minWidth
- val topPadding = max(labelPlaceable.height / 2, bottomPadding)
+ val topPadding = max(heightOrZero(labelPlaceable) / 2, bottomPadding)
val textContraints = incomingConstraints.offset(
horizontal = -occupiedSpaceHorizontally,
vertical = -bottomPadding - topPadding
@@ -455,16 +453,14 @@
leadingPlaceable: Placeable?,
trailingPlaceable: Placeable?,
textFieldPlaceable: Placeable,
- labelPlaceable: Placeable,
+ labelPlaceable: Placeable?,
placeholderPlaceable: Placeable?,
constraints: Constraints
): Int {
- val middleSection = widthOrZero(
- listOf(
- textFieldPlaceable,
- labelPlaceable,
- placeholderPlaceable
- ).maxByOrNull { widthOrZero(it) }
+ val middleSection = maxOf(
+ textFieldPlaceable.width,
+ widthOrZero(labelPlaceable),
+ widthOrZero(placeholderPlaceable)
)
val wrappedWidth =
widthOrZero(leadingPlaceable) + middleSection + widthOrZero(
@@ -481,7 +477,7 @@
leadingPlaceable: Placeable?,
trailingPlaceable: Placeable?,
textFieldPlaceable: Placeable,
- labelPlaceable: Placeable,
+ labelPlaceable: Placeable?,
placeholderPlaceable: Placeable?,
constraints: Constraints,
density: Float
@@ -496,15 +492,15 @@
val topBottomPadding = TextFieldPadding.value * density
val middleSectionHeight = inputFieldHeight + topBottomPadding + max(
topBottomPadding,
- labelPlaceable.height / 2f
+ heightOrZero(labelPlaceable) / 2f
)
return max(
- listOf(
+ constraints.minHeight,
+ maxOf(
heightOrZero(leadingPlaceable),
heightOrZero(trailingPlaceable),
middleSectionHeight.roundToInt()
- ).maxOrNull() ?: 0,
- constraints.minHeight
+ )
)
}
@@ -518,7 +514,7 @@
leadingPlaceable: Placeable?,
trailingPlaceable: Placeable?,
textFieldPlaceable: Placeable,
- labelPlaceable: Placeable,
+ labelPlaceable: Placeable?,
placeholderPlaceable: Placeable?,
animationProgress: Float,
density: Float
@@ -538,12 +534,14 @@
// if animation progress is 0, the label will be centered vertically
// if animation progress is 1, vertically it will be centered to the container's top edge
// horizontally it is placed after the leading icon
- val labelPositionY =
- Alignment.CenterVertically.align(height - labelPlaceable.height) * (1 -
- animationProgress) - (labelPlaceable.height / 2) * animationProgress
- val labelPositionX = (TextFieldPadding.value * density) +
- widthOrZero(leadingPlaceable) * (1 - animationProgress)
- labelPlaceable.placeRelative(labelPositionX.roundToInt(), labelPositionY.roundToInt())
+ if (labelPlaceable != null) {
+ val labelPositionY =
+ Alignment.CenterVertically.align(height - labelPlaceable.height) * (1 -
+ animationProgress) - (labelPlaceable.height / 2) * animationProgress
+ val labelPositionX = (TextFieldPadding.value * density) +
+ widthOrZero(leadingPlaceable) * (1 - animationProgress)
+ labelPlaceable.placeRelative(labelPositionX.roundToInt(), labelPositionY.roundToInt())
+ }
// placed center vertically and after the leading icon horizontally
textFieldPlaceable.placeRelative(
diff --git a/compose/material/material/src/commonMain/kotlin/androidx/compose/material/ProgressIndicator.kt b/compose/material/material/src/commonMain/kotlin/androidx/compose/material/ProgressIndicator.kt
index 6a41b95..8ad5a57 100644
--- a/compose/material/material/src/commonMain/kotlin/androidx/compose/material/ProgressIndicator.kt
+++ b/compose/material/material/src/commonMain/kotlin/androidx/compose/material/ProgressIndicator.kt
@@ -40,12 +40,12 @@
import androidx.compose.ui.graphics.StrokeCap
import androidx.compose.ui.graphics.drawscope.DrawScope
import androidx.compose.ui.graphics.drawscope.Stroke
-import androidx.compose.ui.graphics.vectormath.degrees
import androidx.compose.ui.platform.DensityAmbient
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.LayoutDirection
import androidx.compose.ui.unit.dp
import androidx.compose.ui.util.annotation.FloatRange
+import kotlin.math.PI
import kotlin.math.abs
import kotlin.math.max
@@ -306,7 +306,8 @@
// Length of arc is angle * radius
// Angle (radians) is length / radius
// The length should be the same as the stroke width for calculating the min angle
- val squareStrokeCapOffset = degrees(strokeWidth / (CircularIndicatorDiameter / 2)) / 2
+ val squareStrokeCapOffset =
+ (180.0 / PI).toFloat() * (strokeWidth / (CircularIndicatorDiameter / 2)) / 2f
// Adding a square stroke cap draws half the stroke width behind the start point, so we want to
// move it forward by that amount so the arc visually appears in the correct place
diff --git a/compose/material/material/src/commonMain/kotlin/androidx/compose/material/Scaffold.kt b/compose/material/material/src/commonMain/kotlin/androidx/compose/material/Scaffold.kt
index aa0c15e..01416f5 100644
--- a/compose/material/material/src/commonMain/kotlin/androidx/compose/material/Scaffold.kt
+++ b/compose/material/material/src/commonMain/kotlin/androidx/compose/material/Scaffold.kt
@@ -127,6 +127,9 @@
* screen, by ensuring proper layout strategy for them and collecting necessary data so these
* components will work together correctly.
*
+ * For a similar API which uses a backdrop as the centerpiece of the screen, use the experimental
+ * [BackdropScaffold] component.
+ *
* Simple example of a Scaffold with [TopAppBar], [FloatingActionButton] and drawer:
*
* @sample androidx.compose.material.samples.SimpleScaffoldWithTopBar
diff --git a/compose/material/material/src/commonMain/kotlin/androidx/compose/material/TextField.kt b/compose/material/material/src/commonMain/kotlin/androidx/compose/material/TextField.kt
index 2ebb416..cdb7b94 100644
--- a/compose/material/material/src/commonMain/kotlin/androidx/compose/material/TextField.kt
+++ b/compose/material/material/src/commonMain/kotlin/androidx/compose/material/TextField.kt
@@ -96,12 +96,12 @@
* @param value the input text to be shown in the text field
* @param onValueChange the callback that is triggered when the input service updates the text. An
* updated text comes as a parameter of the callback
- * @param label the label to be displayed inside the text field container. The default text style
- * for internal [Text] is [Typography.caption] when the text field is in focus and
- * [Typography.subtitle1] when the text field is not in focus
* @param modifier a [Modifier] for this text field
* @param textStyle the style to be applied to the input text. The default [textStyle] uses the
* [currentTextStyle] defined by the theme
+ * @param label the optional label to be displayed inside the text field container. The default
+ * text style for internal [Text] is [Typography.caption] when the text field is in focus and
+ * [Typography.subtitle1] when the text field is not in focus
* @param placeholder the optional placeholder to be displayed when the text field is in focus and
* the input text is empty. The default text style for internal [Text] is [Typography.subtitle1]
* @param leadingIcon the optional leading icon to be displayed at the beginning of the text field
@@ -142,9 +142,9 @@
fun TextField(
value: String,
onValueChange: (String) -> Unit,
- label: @Composable () -> Unit,
modifier: Modifier = Modifier,
textStyle: TextStyle = currentTextStyle(),
+ label: @Composable (() -> Unit)? = null,
placeholder: @Composable (() -> Unit)? = null,
leadingIcon: @Composable (() -> Unit)? = null,
trailingIcon: @Composable (() -> Unit)? = null,
@@ -216,12 +216,12 @@
* @param value the input [TextFieldValue] to be shown in the text field
* @param onValueChange the callback that is triggered when the input service updates values in
* [TextFieldValue]. An updated [TextFieldValue] comes as a parameter of the callback
- * @param label the label to be displayed inside the text field container. The default text style
- * for internal [Text] is [Typography.caption] when the text field is in focus and
- * [Typography.subtitle1] when the text field is not in focus
* @param modifier a [Modifier] for this text field
* @param textStyle the style to be applied to the input text. The default [textStyle] uses the
* [currentTextStyle] defined by the theme
+ * @param label the optional label to be displayed inside the text field container. The default
+ * text style for internal [Text] is [Typography.caption] when the text field is in focus and
+ * [Typography.subtitle1] when the text field is not in focus
* @param placeholder the optional placeholder to be displayed when the text field is in focus and
* the input text is empty. The default text style for internal [Text] is [Typography.subtitle1]
* @param leadingIcon the optional leading icon to be displayed at the beginning of the text field
@@ -262,9 +262,9 @@
fun TextField(
value: TextFieldValue,
onValueChange: (TextFieldValue) -> Unit,
- label: @Composable () -> Unit,
modifier: Modifier = Modifier,
textStyle: TextStyle = currentTextStyle(),
+ label: @Composable (() -> Unit)? = null,
placeholder: @Composable (() -> Unit)? = null,
leadingIcon: @Composable (() -> Unit)? = null,
trailingIcon: @Composable (() -> Unit)? = null,
@@ -309,8 +309,8 @@
internal fun TextFieldLayout(
modifier: Modifier = Modifier,
decoratedTextField: @Composable (Modifier) -> Unit,
- decoratedPlaceholder: @Composable (() -> Unit)?,
- decoratedLabel: @Composable () -> Unit,
+ decoratedPlaceholder: @Composable ((Modifier) -> Unit)?,
+ decoratedLabel: @Composable (() -> Unit)?,
leading: @Composable (() -> Unit)?,
trailing: @Composable (() -> Unit)?,
leadingColor: Color,
@@ -351,8 +351,8 @@
private fun IconsWithTextFieldLayout(
modifier: Modifier = Modifier,
textField: @Composable (Modifier) -> Unit,
- label: @Composable () -> Unit,
- placeholder: @Composable (() -> Unit)?,
+ label: @Composable (() -> Unit)?,
+ placeholder: @Composable ((Modifier) -> Unit)?,
leading: @Composable (() -> Unit)?,
trailing: @Composable (() -> Unit)?,
leadingColor: Color,
@@ -379,20 +379,19 @@
}
val padding = Modifier.padding(horizontal = TextFieldPadding)
if (placeholder != null) {
+ placeholder(Modifier.layoutId(PlaceholderId).then(padding))
+ }
+ if (label != null) {
Box(
- modifier = Modifier.layoutId(PlaceholderId).then(padding),
- children = placeholder
+ modifier = Modifier
+ .layoutId(LabelId)
+ .iconPadding(
+ start = TextFieldPadding,
+ end = TextFieldPadding
+ ),
+ children = label
)
}
- Box(
- modifier = Modifier
- .layoutId(LabelId)
- .iconPadding(
- start = TextFieldPadding,
- end = TextFieldPadding
- ),
- children = label
- )
textField(Modifier.layoutId(TextFieldId).then(padding))
},
modifier = modifier
@@ -424,10 +423,10 @@
horizontal = -occupiedSpaceHorizontally
)
val labelPlaceable =
- measurables.first { it.id == LabelId }.measure(labelConstraints)
- val lastBaseline = labelPlaceable[LastBaseline].let {
+ measurables.find { it.id == LabelId }?.measure(labelConstraints)
+ val lastBaseline = labelPlaceable?.get(LastBaseline)?.let {
if (it != AlignmentLine.Unspecified) it else labelPlaceable.height
- }
+ } ?: 0
val effectiveLabelBaseline = max(lastBaseline, baseLineOffset)
// measure input field
@@ -466,7 +465,7 @@
)
layout(width, height) {
- if (labelPlaceable.width != 0) {
+ if (widthOrZero(labelPlaceable) != 0) {
val labelEndPosition =
(baseLineOffset - lastBaseline).coerceAtLeast(0)
place(
@@ -500,13 +499,13 @@
leadingPlaceable: Placeable?,
trailingPlaceable: Placeable?,
textFieldPlaceable: Placeable,
- labelPlaceable: Placeable,
+ labelPlaceable: Placeable?,
placeholderPlaceable: Placeable?,
constraints: Constraints
): Int {
val middleSection = maxOf(
textFieldPlaceable.width,
- labelPlaceable.width,
+ widthOrZero(labelPlaceable),
widthOrZero(placeholderPlaceable)
)
val wrappedWidth =
@@ -544,7 +543,7 @@
width: Int,
height: Int,
textfieldPlaceable: Placeable,
- labelPlaceable: Placeable,
+ labelPlaceable: Placeable?,
placeholderPlaceable: Placeable?,
leadingPlaceable: Placeable?,
trailingPlaceable: Placeable?,
@@ -560,17 +559,18 @@
width - trailingPlaceable.width,
Alignment.CenterVertically.align(height - trailingPlaceable.height)
)
- val labelCenterPosition = Alignment.CenterStart.align(
- IntSize(
- width - labelPlaceable.width,
- height - labelPlaceable.height
+ if (labelPlaceable != null) {
+ val labelCenterPosition = Alignment.CenterStart.align(
+ IntSize(
+ width - labelPlaceable.width,
+ height - labelPlaceable.height
+ )
)
- )
- val labelDistance = labelCenterPosition.y - labelEndPosition
- val labelPositionY =
- labelCenterPosition.y - (labelDistance * animationProgress).roundToInt()
- labelPlaceable.placeRelative(widthOrZero(leadingPlaceable), labelPositionY)
-
+ val labelDistance = labelCenterPosition.y - labelEndPosition
+ val labelPositionY =
+ labelCenterPosition.y - (labelDistance * animationProgress).roundToInt()
+ labelPlaceable.placeRelative(widthOrZero(leadingPlaceable), labelPositionY)
+ }
textfieldPlaceable.placeRelative(widthOrZero(leadingPlaceable), textPosition)
placeholderPlaceable?.placeRelative(widthOrZero(leadingPlaceable), textPosition)
}
diff --git a/compose/material/material/src/commonMain/kotlin/androidx/compose/material/TextFieldImpl.kt b/compose/material/material/src/commonMain/kotlin/androidx/compose/material/TextFieldImpl.kt
index 24923ab..82f0482 100644
--- a/compose/material/material/src/commonMain/kotlin/androidx/compose/material/TextFieldImpl.kt
+++ b/compose/material/material/src/commonMain/kotlin/androidx/compose/material/TextFieldImpl.kt
@@ -22,11 +22,13 @@
import androidx.compose.animation.ColorPropKey
import androidx.compose.animation.DpPropKey
import androidx.compose.animation.core.FloatPropKey
+import androidx.compose.animation.core.LinearEasing
import androidx.compose.animation.core.TransitionSpec
import androidx.compose.animation.core.transitionDefinition
import androidx.compose.animation.core.tween
import androidx.compose.animation.transition
import androidx.compose.foundation.BaseTextField
+import androidx.compose.foundation.Box
import androidx.compose.foundation.ContentColorAmbient
import androidx.compose.foundation.ExperimentalFoundationApi
import androidx.compose.foundation.ProvideTextStyle
@@ -52,6 +54,7 @@
import androidx.compose.ui.Modifier
import androidx.compose.ui.Placeable
import androidx.compose.ui.draw.clipToBounds
+import androidx.compose.ui.draw.drawOpacity
import androidx.compose.ui.focus.ExperimentalFocus
import androidx.compose.ui.focus.FocusRequester
import androidx.compose.ui.focus.isFocused
@@ -95,7 +98,7 @@
onValueChange: (TextFieldValue) -> Unit,
modifier: Modifier,
textStyle: TextStyle,
- label: @Composable () -> Unit,
+ label: @Composable (() -> Unit)?,
placeholder: @Composable (() -> Unit)?,
leading: @Composable (() -> Unit)?,
trailing: @Composable (() -> Unit)?,
@@ -114,28 +117,12 @@
val keyboardController: Ref<SoftwareKeyboardController> = remember { Ref() }
var isFocused by remember { mutableStateOf(false) }
- val inputState by remember(value.text, isFocused) {
- mutableStateOf(
- when {
- isFocused -> InputPhase.Focused
- value.text.isEmpty() -> InputPhase.UnfocusedEmpty
- else -> InputPhase.UnfocusedNotEmpty
- }
- )
+ val inputState = when {
+ isFocused -> InputPhase.Focused
+ value.text.isEmpty() -> InputPhase.UnfocusedEmpty
+ else -> InputPhase.UnfocusedNotEmpty
}
- val decoratedPlaceholder: @Composable (() -> Unit)? =
- if (placeholder != null && inputState == InputPhase.Focused && value.text.isEmpty()) {
- {
- Decoration(
- contentColor = inactiveColor,
- typography = MaterialTheme.typography.subtitle1,
- emphasis = EmphasisAmbient.current.medium,
- children = placeholder
- )
- }
- } else null
-
val decoratedTextField = @Composable { tagModifier: Modifier ->
Decoration(
contentColor = inactiveColor,
@@ -188,6 +175,7 @@
TextFieldTransitionScope.transition(
inputState = inputState,
+ showLabel = label != null,
activeColor = if (isErrorValue) {
errorColor
} else {
@@ -195,24 +183,40 @@
},
labelInactiveColor = emphasisLevels.medium.applyEmphasis(inactiveColor),
indicatorInactiveColor = inactiveColor.applyAlpha(alpha = IndicatorInactiveAlpha)
- ) { labelProgress, animatedLabelColor, indicatorWidth, animatedIndicatorColor ->
+ ) { labelProgress, animatedLabelColor, indicatorWidth, indicatorColor, placeholderOpacity ->
- val leadingColor =
- inactiveColor.applyAlpha(alpha = TrailingLeadingAlpha)
+ val leadingColor = inactiveColor.applyAlpha(alpha = TrailingLeadingAlpha)
val trailingColor = if (isErrorValue) errorColor else leadingColor
- val decoratedLabel = @Composable {
- val labelAnimatedStyle = lerp(
- MaterialTheme.typography.subtitle1,
- MaterialTheme.typography.caption,
- labelProgress
- )
- Decoration(
- contentColor = animatedLabelColor,
- typography = labelAnimatedStyle,
- children = label
- )
- }
+ val decoratedLabel: @Composable (() -> Unit)? =
+ if (label != null) {
+ {
+ val labelAnimatedStyle = lerp(
+ MaterialTheme.typography.subtitle1,
+ MaterialTheme.typography.caption,
+ labelProgress
+ )
+ Decoration(
+ contentColor = animatedLabelColor,
+ typography = labelAnimatedStyle,
+ children = label
+ )
+ }
+ } else null
+
+ val decoratedPlaceholder: @Composable ((Modifier) -> Unit)? =
+ if (placeholder != null && value.text.isEmpty()) {
+ { modifier ->
+ Box(modifier.drawOpacity(placeholderOpacity)) {
+ Decoration(
+ contentColor = inactiveColor,
+ typography = MaterialTheme.typography.subtitle1,
+ emphasis = EmphasisAmbient.current.medium,
+ children = placeholder
+ )
+ }
+ }
+ } else null
when (type) {
TextFieldType.Filled -> {
@@ -232,7 +236,7 @@
trailingColor = trailingColor,
labelProgress = labelProgress,
indicatorWidth = indicatorWidth,
- indicatorColor = animatedIndicatorColor,
+ indicatorColor = indicatorColor,
backgroundColor = backgroundColor,
shape = shape
)
@@ -255,7 +259,7 @@
trailingColor = trailingColor,
labelProgress = labelProgress,
indicatorWidth = indicatorWidth,
- indicatorColor = animatedIndicatorColor
+ indicatorColor = indicatorColor
)
}
}
@@ -385,10 +389,12 @@
private val LabelProgressProp = FloatPropKey()
private val IndicatorColorProp = ColorPropKey()
private val IndicatorWidthProp = DpPropKey()
+ private val PlaceholderOpacityProp = FloatPropKey()
@Composable
fun transition(
inputState: InputPhase,
+ showLabel: Boolean,
activeColor: Color,
labelInactiveColor: Color,
indicatorInactiveColor: Color,
@@ -396,11 +402,18 @@
labelProgress: Float,
labelColor: Color,
indicatorWidth: Dp,
- indicatorColor: Color
+ indicatorColor: Color,
+ placeholderOpacity: Float
) -> Unit
) {
- val definition = remember(activeColor, labelInactiveColor, indicatorInactiveColor) {
+ val definition = remember(
+ showLabel,
+ activeColor,
+ labelInactiveColor,
+ indicatorInactiveColor
+ ) {
generateLabelTransitionDefinition(
+ showLabel,
activeColor,
labelInactiveColor,
indicatorInactiveColor
@@ -411,11 +424,13 @@
state[LabelProgressProp],
state[LabelColorProp],
state[IndicatorWidthProp],
- state[IndicatorColorProp]
+ state[IndicatorColorProp],
+ state[PlaceholderOpacityProp]
)
}
private fun generateLabelTransitionDefinition(
+ showLabel: Boolean,
activeColor: Color,
labelInactiveColor: Color,
indicatorInactiveColor: Color
@@ -424,26 +439,28 @@
this[LabelColorProp] = activeColor
this[IndicatorColorProp] = activeColor
this[LabelProgressProp] = 1f
- this[IndicatorWidthProp] =
- IndicatorFocusedWidth
+ this[IndicatorWidthProp] = IndicatorFocusedWidth
+ this[PlaceholderOpacityProp] = 1f
}
state(InputPhase.UnfocusedEmpty) {
this[LabelColorProp] = labelInactiveColor
this[IndicatorColorProp] = indicatorInactiveColor
this[LabelProgressProp] = 0f
- this[IndicatorWidthProp] =
- IndicatorUnfocusedWidth
+ this[IndicatorWidthProp] = IndicatorUnfocusedWidth
+ this[PlaceholderOpacityProp] = if (showLabel) 0f else 1f
}
state(InputPhase.UnfocusedNotEmpty) {
this[LabelColorProp] = labelInactiveColor
this[IndicatorColorProp] = indicatorInactiveColor
this[LabelProgressProp] = 1f
this[IndicatorWidthProp] = 1.dp
+ this[PlaceholderOpacityProp] = 0f
}
transition(fromState = InputPhase.Focused, toState = InputPhase.UnfocusedEmpty) {
labelTransition()
indicatorTransition()
+ placeholderDisappearTransition()
}
transition(fromState = InputPhase.Focused, toState = InputPhase.UnfocusedNotEmpty) {
indicatorTransition()
@@ -454,11 +471,13 @@
transition(fromState = InputPhase.UnfocusedEmpty, toState = InputPhase.Focused) {
labelTransition()
indicatorTransition()
+ placeholderAppearTransition()
}
// below states are needed to support case when a single state is used to control multiple
// text fields.
transition(fromState = InputPhase.UnfocusedNotEmpty, toState = InputPhase.UnfocusedEmpty) {
labelTransition()
+ placeholderAppearTransition()
}
transition(fromState = InputPhase.UnfocusedEmpty, toState = InputPhase.UnfocusedNotEmpty) {
labelTransition()
@@ -474,6 +493,21 @@
LabelColorProp using tween(durationMillis = AnimationDuration)
LabelProgressProp using tween(durationMillis = AnimationDuration)
}
+
+ private fun TransitionSpec<InputPhase>.placeholderAppearTransition() {
+ PlaceholderOpacityProp using tween(
+ durationMillis = PlaceholderAnimationDuration,
+ delayMillis = PlaceholderAnimationDelayOrDuration,
+ easing = LinearEasing
+ )
+ }
+
+ private fun TransitionSpec<InputPhase>.placeholderDisappearTransition() {
+ PlaceholderOpacityProp using tween(
+ durationMillis = PlaceholderAnimationDelayOrDuration,
+ easing = LinearEasing
+ )
+ }
}
/**
@@ -482,8 +516,10 @@
private enum class InputPhase {
// Text field is focused
Focused,
+
// Text field is not focused and input text is empty
UnfocusedEmpty,
+
// Text field is not focused but input text is not empty
UnfocusedNotEmpty
}
@@ -493,6 +529,9 @@
internal const val LabelId = "Label"
private const val AnimationDuration = 150
+private const val PlaceholderAnimationDuration = 83
+private const val PlaceholderAnimationDelayOrDuration = 67
+
private val IndicatorUnfocusedWidth = 1.dp
private val IndicatorFocusedWidth = 2.dp
private const val IndicatorInactiveAlpha = 0.42f
diff --git a/compose/material/material/src/commonMain/kotlin/androidx/compose/material/ripple/RippleIndication.kt b/compose/material/material/src/commonMain/kotlin/androidx/compose/material/ripple/RippleIndication.kt
index 7738a1c..b2b1357 100644
--- a/compose/material/material/src/commonMain/kotlin/androidx/compose/material/ripple/RippleIndication.kt
+++ b/compose/material/material/src/commonMain/kotlin/androidx/compose/material/ripple/RippleIndication.kt
@@ -143,7 +143,7 @@
}
@OptIn(ExperimentalMaterialApi::class)
-private class RippleIndicationInstance internal constructor(
+private class RippleIndicationInstance constructor(
private val bounded: Boolean,
private val radius: Dp? = null,
private var color: State<Color>,
@@ -171,8 +171,6 @@
addRipple(targetRadius, pressPosition)
}
} else {
- // TODO: possibly handle cancelling the animation here, need to clarify spec for when
- // ripples and state layers overlap
removeRipple()
}
drawRipples(color)
@@ -216,6 +214,29 @@
}
}
+/**
+ * Represents the layer underneath the press ripple, that displays an overlay for states such as
+ * [Interaction.Dragged].
+ *
+ * Typically, there should be both an 'incoming' and an 'outgoing' layer, so that when
+ * transitioning between two states, the incoming of the new state, and the outgoing of the old
+ * state can be displayed. However, because:
+ *
+ * a) the duration of these outgoing transitions are so short (mostly 15ms, which is less than 1
+ * frame at 60fps), and hence are barely noticeable if they happen at the same time as an
+ * incoming transition
+ * b) two layers cause a lot of extra work, and related performance concerns
+ *
+ * We skip managing two layers, and instead only show one layer. The details for the
+ * [AnimationSpec]s used are as follows:
+ *
+ * No state -> a state = incoming transition for the new state
+ * A state -> a different state = incoming transition for the new state
+ * A state -> no state = outgoing transition for the old state
+ *
+ * @see IncomingStateLayerAnimationSpecs
+ * @see OutgoingStateLayerAnimationSpecs
+ */
@OptIn(ExperimentalMaterialApi::class)
private class StateLayer(
clock: AnimationClockObservable,
@@ -234,26 +255,28 @@
val currentInteractions = interactionState.value
var handled = false
- // Handle a new interaction
- for (interaction in currentInteractions) {
+ // Handle a new interaction, starting from the end as we care about the most recent
+ // interaction, not the oldest interaction.
+ for (interaction in currentInteractions.reversed()) {
// Stop looping if we have already moved to a new state
if (handled) break
- // Move to the next interaction if this interaction is not a new interaction
- if (interaction in previousInteractions) continue
-
// Pressed state is explicitly handled with a ripple animation, and not a state layer
if (interaction is Interaction.Pressed) continue
+ // Move to the next interaction if this interaction is not a new interaction
+ if (interaction in previousInteractions) continue
+
// Move to the next interaction if this is not an interaction we show a state layer for
val targetOpacity = rippleOpacity.opacityForInteraction(interaction)
if (targetOpacity == 0f) continue
- val animationSpec = animationSpecForInteraction(interaction)
- animatedOpacity.animateTo(
- targetOpacity,
- animationSpec
- )
+ // TODO: consider defaults - these will be used for a custom Interaction that we are
+ // not aware of, but has an alpha that should be shown because of a custom RippleTheme.
+ val incomingAnimationSpec = IncomingStateLayerAnimationSpecs[interaction]
+ ?: TweenSpec(durationMillis = 15, easing = LinearEasing)
+
+ animatedOpacity.animateTo(targetOpacity, incomingAnimationSpec)
lastDrawnInteraction = interaction
handled = true
@@ -263,10 +286,13 @@
if (!handled) {
val previousInteraction = lastDrawnInteraction
if (previousInteraction != null && previousInteraction !in currentInteractions) {
- animatedOpacity.animateTo(
- 0f,
- animationSpecForInteraction(previousInteraction)
- )
+ // TODO: consider defaults - these will be used for a custom Interaction that we are
+ // not aware of, but has an alpha that should be shown because of a custom
+ // RippleTheme.
+ val outgoingAnimationSpec = OutgoingStateLayerAnimationSpecs[previousInteraction]
+ ?: TweenSpec(durationMillis = 15, easing = LinearEasing)
+
+ animatedOpacity.animateTo(0f, outgoingAnimationSpec)
lastDrawnInteraction = null
}
@@ -288,17 +314,31 @@
}
}
}
-
- /**
- * TODO: handle [interaction] for hover / focus states
- */
- @Suppress("UNUSED_PARAMETER")
- private fun animationSpecForInteraction(
- interaction: Interaction
- ): AnimationSpec<Float> {
- return TweenSpec(
- durationMillis = 15,
- easing = LinearEasing
- )
- }
}
+
+/**
+ * [AnimationSpec]s used when transitioning to a new state, either from a previous state, or no
+ * state.
+ *
+ * TODO: handle hover / focus states
+ */
+private val IncomingStateLayerAnimationSpecs: Map<Interaction, AnimationSpec<Float>> = mapOf(
+ // TODO: b/161522042 - clarify specs for dragged state transitions
+ Interaction.Dragged to TweenSpec(
+ durationMillis = 45,
+ easing = LinearEasing
+ )
+)
+
+/**
+ * [AnimationSpec]s used when transitioning away from a state, to no state.
+ *
+ * TODO: handle hover / focus states
+ */
+private val OutgoingStateLayerAnimationSpecs: Map<Interaction, AnimationSpec<Float>> = mapOf(
+ // TODO: b/161522042 - clarify specs for dragged state transitions
+ Interaction.Dragged to TweenSpec(
+ durationMillis = 150,
+ easing = LinearEasing
+ )
+)
diff --git a/compose/navigation/navigation/api/current.txt b/compose/navigation/navigation/api/current.txt
index da4f6cc..5202fb7 100644
--- a/compose/navigation/navigation/api/current.txt
+++ b/compose/navigation/navigation/api/current.txt
@@ -1 +1,9 @@
// Signature format: 3.0
+package androidx.compose.navigation {
+
+ public final class NavHostControllerKt {
+ method @androidx.compose.runtime.Composable public static androidx.compose.runtime.State<androidx.navigation.NavBackStackEntry> currentBackStackEntryAsState(androidx.navigation.NavController);
+ }
+
+}
+
diff --git a/compose/navigation/navigation/api/public_plus_experimental_current.txt b/compose/navigation/navigation/api/public_plus_experimental_current.txt
index da4f6cc..5202fb7 100644
--- a/compose/navigation/navigation/api/public_plus_experimental_current.txt
+++ b/compose/navigation/navigation/api/public_plus_experimental_current.txt
@@ -1 +1,9 @@
// Signature format: 3.0
+package androidx.compose.navigation {
+
+ public final class NavHostControllerKt {
+ method @androidx.compose.runtime.Composable public static androidx.compose.runtime.State<androidx.navigation.NavBackStackEntry> currentBackStackEntryAsState(androidx.navigation.NavController);
+ }
+
+}
+
diff --git a/compose/navigation/navigation/api/restricted_current.txt b/compose/navigation/navigation/api/restricted_current.txt
index da4f6cc..5202fb7 100644
--- a/compose/navigation/navigation/api/restricted_current.txt
+++ b/compose/navigation/navigation/api/restricted_current.txt
@@ -1 +1,9 @@
// Signature format: 3.0
+package androidx.compose.navigation {
+
+ public final class NavHostControllerKt {
+ method @androidx.compose.runtime.Composable public static androidx.compose.runtime.State<androidx.navigation.NavBackStackEntry> currentBackStackEntryAsState(androidx.navigation.NavController);
+ }
+
+}
+
diff --git a/compose/navigation/navigation/build.gradle b/compose/navigation/navigation/build.gradle
index d48190ae..439e4e0 100644
--- a/compose/navigation/navigation/build.gradle
+++ b/compose/navigation/navigation/build.gradle
@@ -32,8 +32,12 @@
kotlinPlugin project(path: ":compose:compose-compiler")
implementation(KOTLIN_STDLIB)
+ api project(":compose:runtime:runtime")
+ api project(":compose:ui:ui")
api "androidx.navigation:navigation-runtime-ktx:2.3.0"
+ androidTestImplementation 'androidx.navigation:navigation-testing:2.3.0'
+ androidTestImplementation project(':ui:ui-test')
androidTestImplementation(ANDROIDX_TEST_RUNNER)
androidTestImplementation(JUNIT)
androidTestImplementation(TRUTH)
diff --git a/compose/navigation/navigation/src/androidTest/java/androidx/compose/navigation/NavHostControllerTest.kt b/compose/navigation/navigation/src/androidTest/java/androidx/compose/navigation/NavHostControllerTest.kt
new file mode 100644
index 0000000..988539c
--- /dev/null
+++ b/compose/navigation/navigation/src/androidTest/java/androidx/compose/navigation/NavHostControllerTest.kt
@@ -0,0 +1,126 @@
+/*
+ * Copyright 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.compose.navigation
+
+import androidx.annotation.IdRes
+import androidx.compose.runtime.State
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.ui.platform.ContextAmbient
+import androidx.navigation.NavBackStackEntry
+import androidx.navigation.NavController
+import androidx.navigation.NavDestination
+import androidx.navigation.NavDestinationBuilder
+import androidx.navigation.NavGraphBuilder
+import androidx.navigation.createGraph
+import androidx.navigation.get
+import androidx.navigation.testing.TestNavHostController
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.LargeTest
+import androidx.ui.test.createComposeRule
+import androidx.ui.test.runOnUiThread
+import com.google.common.truth.Truth.assertWithMessage
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@ExperimentalCoroutinesApi
+@LargeTest
+@RunWith(AndroidJUnit4::class)
+class NavHostControllerTest {
+ @get:Rule
+ val composeTestRule = createComposeRule()
+
+ @Test
+ fun testCurrentBackStackEntrySetGraph() {
+ var currentBackStackEntry: State<NavBackStackEntry?> = mutableStateOf(null)
+ composeTestRule.setContent {
+ val navController = TestNavHostController(ContextAmbient.current)
+
+ navController.graph = navController.createGraph(startDestination = FIRST_DESTINATION) {
+ test(FIRST_DESTINATION)
+ }
+
+ currentBackStackEntry = navController.currentBackStackEntryAsState()
+ }
+
+ assertWithMessage("the currentBackStackEntry should be set with the graph")
+ .that(currentBackStackEntry.value?.destination?.id)
+ .isEqualTo(FIRST_DESTINATION)
+ }
+
+ @Test
+ fun testCurrentBackStackEntryNavigate() {
+ var currentBackStackEntry: State<NavBackStackEntry?> = mutableStateOf(null)
+ lateinit var navController: NavController
+ composeTestRule.setContent {
+ navController = TestNavHostController(ContextAmbient.current)
+
+ navController.graph = navController.createGraph(startDestination = FIRST_DESTINATION) {
+ test(FIRST_DESTINATION)
+ test(SECOND_DESTINATION)
+ }
+
+ currentBackStackEntry = navController.currentBackStackEntryAsState()
+ }
+
+ assertWithMessage("the currentBackStackEntry should be set with the graph")
+ .that(currentBackStackEntry.value?.destination?.id)
+ .isEqualTo(FIRST_DESTINATION)
+
+ runOnUiThread {
+ navController.navigate(SECOND_DESTINATION)
+ }
+
+ assertWithMessage("the currentBackStackEntry should be after navigate")
+ .that(currentBackStackEntry.value?.destination?.id)
+ .isEqualTo(SECOND_DESTINATION)
+ }
+
+ @Test
+ fun testCurrentBackStackEntryPop() {
+ var currentBackStackEntry: State<NavBackStackEntry?> = mutableStateOf(null)
+ lateinit var navController: TestNavHostController
+ composeTestRule.setContent {
+ navController = TestNavHostController(ContextAmbient.current)
+
+ navController.graph = navController.createGraph(startDestination = FIRST_DESTINATION) {
+ test(FIRST_DESTINATION)
+ test(SECOND_DESTINATION)
+ }
+
+ currentBackStackEntry = navController.currentBackStackEntryAsState()
+ }
+
+ runOnUiThread {
+ navController.setCurrentDestination(SECOND_DESTINATION)
+ navController.popBackStack()
+ }
+
+ assertWithMessage("the currentBackStackEntry should return to first destination after pop")
+ .that(currentBackStackEntry.value?.destination?.id)
+ .isEqualTo(FIRST_DESTINATION)
+ }
+}
+
+inline fun NavGraphBuilder.test(
+ @IdRes id: Int,
+ builder: NavDestinationBuilder<NavDestination>.() -> Unit = { }
+) = destination(NavDestinationBuilder<NavDestination>(provider["test"], id).apply(builder))
+
+private const val FIRST_DESTINATION = 1
+private const val SECOND_DESTINATION = 2
diff --git a/compose/navigation/navigation/src/main/java/androidx/compose/navigation/NavHostController.kt b/compose/navigation/navigation/src/main/java/androidx/compose/navigation/NavHostController.kt
new file mode 100644
index 0000000..d4285e6
--- /dev/null
+++ b/compose/navigation/navigation/src/main/java/androidx/compose/navigation/NavHostController.kt
@@ -0,0 +1,51 @@
+/*
+ * Copyright 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.compose.navigation
+
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.MutableState
+import androidx.compose.runtime.State
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.onCommit
+import androidx.compose.runtime.remember
+import androidx.navigation.NavBackStackEntry
+import androidx.navigation.NavController
+
+/**
+ * Gets the current navigation back stack entry as a [MutableState]. When the given navController
+ * changes the back stack due to a [NavController.navigate] or [NavController.popBackStack] this
+ * will trigger a recompose and return the top entry on the back stack.
+ *
+ * @return a mutable state of the current back stack entry
+ */
+@Composable
+fun NavController.currentBackStackEntryAsState(): State<NavBackStackEntry?> {
+ val currentNavBackStackEntry = remember { mutableStateOf(currentBackStackEntry) }
+ // setup the onDestinationChangedListener responsible for detecting when the
+ // current back stack entry changes
+ onCommit(this) {
+ val callback = NavController.OnDestinationChangedListener { controller, _, _ ->
+ currentNavBackStackEntry.value = controller.currentBackStackEntry
+ }
+ addOnDestinationChangedListener(callback)
+ // remove the navController on dispose (i.e. when the composable is destroyed)
+ onDispose {
+ removeOnDestinationChangedListener(callback)
+ }
+ }
+ return currentNavBackStackEntry
+}
diff --git a/compose/runtime/runtime-dispatch/src/desktopMain/kotlin/androidx/compose/runtime/dispatch/DesktopUiDispatcher.kt b/compose/runtime/runtime-dispatch/src/desktopMain/kotlin/androidx/compose/runtime/dispatch/DesktopUiDispatcher.kt
index a2e7bdd..04d74ea 100644
--- a/compose/runtime/runtime-dispatch/src/desktopMain/kotlin/androidx/compose/runtime/dispatch/DesktopUiDispatcher.kt
+++ b/compose/runtime/runtime-dispatch/src/desktopMain/kotlin/androidx/compose/runtime/dispatch/DesktopUiDispatcher.kt
@@ -28,12 +28,7 @@
private typealias Queue = ArrayList<Action>
/**
- * Ticks scheduler for Desktop. It tries to mimic Android's Choreographer and has multiple levels of
- * callbacks: "just" callbacks and "after" callbacks (Choreographer has more, but two is enough for us)
- * It's necessary because recomposition and drawing should be synchronized, otherwise races and
- * inconsistencies are possible. On Android, this synchronization is achieved implicitly by executing
- * recomposition and scheduling of redrawing as different types of callbacks. It guarantees,
- * that after recomposition, redrawing happens in the exactly same tick.
+ * Ticks scheduler for Desktop. It tries to mimic Android's Choreographer.
*
* There are some plans to make possible redrawing based on composition snapshots,
* so maybe some requirements for dispatcher will be mitigated in the future.
@@ -42,14 +37,13 @@
private val lock = Any()
@PublishedApi internal val callbackLock = Any()
private var callbacks = Queue()
- private var afterCallbacks = Queue()
@Volatile
private var scheduled = false
private fun scheduleIfNeeded() {
synchronized(lock) {
- if (!scheduled && (callbacks.isNotEmpty() || afterCallbacks.isNotEmpty())) {
+ if (!scheduled && (callbacks.isNotEmpty())) {
invokeLater { tick() }
scheduled = true
}
@@ -80,7 +74,6 @@
scheduled = false
val now = System.nanoTime()
runCallbacks(now, callbacks)
- runCallbacks(now, afterCallbacks)
scheduleIfNeeded()
}
@@ -91,12 +84,6 @@
}
}
- fun scheduleAfterCallback(action: Action) {
- synchronized(lock) {
- afterCallbacks.add(action)
- }
- }
-
fun removeCallback(action: (Long) -> Unit) {
synchronized(lock) {
callbacks.remove(action)
diff --git a/compose/runtime/runtime-livedata/build.gradle b/compose/runtime/runtime-livedata/build.gradle
index f91decc..825bf92 100644
--- a/compose/runtime/runtime-livedata/build.gradle
+++ b/compose/runtime/runtime-livedata/build.gradle
@@ -46,7 +46,7 @@
androidx {
name = "Compose LiveData integration"
publish = Publish.SNAPSHOT_AND_RELEASE
- mavenVersion = LibraryVersions.UI
+ mavenVersion = LibraryVersions.COMPOSE
mavenGroup = LibraryGroups.Compose.RUNTIME
inceptionYear = "2020"
description = "Compose integration with LiveData"
diff --git a/compose/runtime/runtime-rxjava2/build.gradle b/compose/runtime/runtime-rxjava2/build.gradle
index 5311fee..1afca61 100644
--- a/compose/runtime/runtime-rxjava2/build.gradle
+++ b/compose/runtime/runtime-rxjava2/build.gradle
@@ -45,7 +45,7 @@
androidx {
name = "Compose RxJava 2 integration"
publish = Publish.SNAPSHOT_AND_RELEASE
- mavenVersion = LibraryVersions.UI
+ mavenVersion = LibraryVersions.COMPOSE
mavenGroup = LibraryGroups.Compose.RUNTIME
inceptionYear = "2020"
description = "Compose integration with RxJava 2"
diff --git a/compose/runtime/runtime-saved-instance-state/build.gradle b/compose/runtime/runtime-saved-instance-state/build.gradle
index b8ad72b..690ee6a 100644
--- a/compose/runtime/runtime-saved-instance-state/build.gradle
+++ b/compose/runtime/runtime-saved-instance-state/build.gradle
@@ -78,7 +78,7 @@
androidx {
name = "Compose Saved Instance State support"
publish = Publish.SNAPSHOT_AND_RELEASE
- mavenVersion = LibraryVersions.UI
+ mavenVersion = LibraryVersions.COMPOSE
mavenGroup = LibraryGroups.Compose.RUNTIME
inceptionYear = "2020"
description = "Compose components that allow saving and restoring the local ui state"
diff --git a/compose/runtime/runtime/api/current.txt b/compose/runtime/runtime/api/current.txt
index 2f40713..3744467 100644
--- a/compose/runtime/runtime/api/current.txt
+++ b/compose/runtime/runtime/api/current.txt
@@ -884,6 +884,11 @@
field public static final androidx.compose.runtime.snapshots.SnapshotApplyResult.Success INSTANCE;
}
+ public final class SnapshotFlowKt {
+ method @androidx.compose.runtime.ExperimentalComposeApi public static <T> kotlinx.coroutines.flow.Flow<T> snapshotFlow(kotlin.jvm.functions.Function0<? extends T> block);
+ method @androidx.compose.runtime.ExperimentalComposeApi public static inline <R> R! withMutableSnapshot(kotlin.jvm.functions.Function0<? extends R> block);
+ }
+
public final class SnapshotIdSetKt {
}
diff --git a/compose/runtime/runtime/api/public_plus_experimental_current.txt b/compose/runtime/runtime/api/public_plus_experimental_current.txt
index 2f40713..3744467 100644
--- a/compose/runtime/runtime/api/public_plus_experimental_current.txt
+++ b/compose/runtime/runtime/api/public_plus_experimental_current.txt
@@ -884,6 +884,11 @@
field public static final androidx.compose.runtime.snapshots.SnapshotApplyResult.Success INSTANCE;
}
+ public final class SnapshotFlowKt {
+ method @androidx.compose.runtime.ExperimentalComposeApi public static <T> kotlinx.coroutines.flow.Flow<T> snapshotFlow(kotlin.jvm.functions.Function0<? extends T> block);
+ method @androidx.compose.runtime.ExperimentalComposeApi public static inline <R> R! withMutableSnapshot(kotlin.jvm.functions.Function0<? extends R> block);
+ }
+
public final class SnapshotIdSetKt {
}
diff --git a/compose/runtime/runtime/api/restricted_current.txt b/compose/runtime/runtime/api/restricted_current.txt
index 1270ce0..7240288 100644
--- a/compose/runtime/runtime/api/restricted_current.txt
+++ b/compose/runtime/runtime/api/restricted_current.txt
@@ -918,6 +918,11 @@
field public static final androidx.compose.runtime.snapshots.SnapshotApplyResult.Success INSTANCE;
}
+ public final class SnapshotFlowKt {
+ method @androidx.compose.runtime.ExperimentalComposeApi public static <T> kotlinx.coroutines.flow.Flow<T> snapshotFlow(kotlin.jvm.functions.Function0<? extends T> block);
+ method @androidx.compose.runtime.ExperimentalComposeApi public static inline <R> R! withMutableSnapshot(kotlin.jvm.functions.Function0<? extends R> block);
+ }
+
public final class SnapshotIdSetKt {
}
diff --git a/compose/runtime/runtime/samples/src/main/java/androidx/compose/runtime/samples/SnapshotSamples.kt b/compose/runtime/runtime/samples/src/main/java/androidx/compose/runtime/samples/SnapshotSamples.kt
new file mode 100644
index 0000000..1e62c6d
--- /dev/null
+++ b/compose/runtime/runtime/samples/src/main/java/androidx/compose/runtime/samples/SnapshotSamples.kt
@@ -0,0 +1,61 @@
+/*
+ * Copyright 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.compose.runtime.samples
+
+import androidx.annotation.Sampled
+import androidx.compose.runtime.ExperimentalComposeApi
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.setValue
+import androidx.compose.runtime.snapshots.snapshotFlow
+import androidx.compose.runtime.snapshots.withMutableSnapshot
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.flow.collect
+import kotlinx.coroutines.launch
+
+@Suppress("UNREACHABLE_CODE", "CanBeVal", "UNUSED_VARIABLE")
+@OptIn(ExperimentalComposeApi::class)
+@Sampled
+fun snapshotFlowSample() {
+ // Define Snapshot state objects
+ var greeting by mutableStateOf("Hello")
+ var person by mutableStateOf("Adam")
+
+ // ...
+
+ // Create a flow that will emit whenever our person-specific greeting changes
+ val greetPersonFlow = snapshotFlow { "$greeting, $person" }
+
+ // ...
+
+ val collectionScope: CoroutineScope = TODO("Use your scope here")
+
+ // Collect the flow and offer greetings!
+ collectionScope.launch {
+ greetPersonFlow.collect {
+ println(greeting)
+ }
+ }
+
+ // ...
+
+ // Change snapshot state; greetPersonFlow will emit a new greeting
+ withMutableSnapshot {
+ greeting = "Ahoy"
+ person = "Sean"
+ }
+}
diff --git a/compose/runtime/runtime/src/androidAndroidTest/kotlin/androidx/compose/runtime/EffectsTests.kt b/compose/runtime/runtime/src/androidAndroidTest/kotlin/androidx/compose/runtime/EffectsTests.kt
index 07100b2..8654519 100644
--- a/compose/runtime/runtime/src/androidAndroidTest/kotlin/androidx/compose/runtime/EffectsTests.kt
+++ b/compose/runtime/runtime/src/androidAndroidTest/kotlin/androidx/compose/runtime/EffectsTests.kt
@@ -700,15 +700,20 @@
)
enabler = true
}.then { activity ->
- assertEquals(
- "0",
- (activity.findViewById(id) as? TextView)?.text
- )
+ val text = (activity.findViewById(id) as? TextView)?.text
+
+ // Text could be either "0" or "1" here depending on timing. It is most likely still
+ // "0" but could be "1" if the recompose implied by the onCommit lands before this
+ // code runs.
+ assertTrue(text == "0" || text == "1")
}.then { activity ->
+ // Here this should run after the recompose lands.
assertEquals(
"1",
(activity.findViewById(id) as? TextView)?.text
)
+
+ // Cause the state to advance.
(activity.findViewById<Button>(id + 1)?.callOnClick())
}.then { activity ->
assertEquals(
diff --git a/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/Composer.kt b/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/Composer.kt
index 80bc173..72580db 100644
--- a/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/Composer.kt
+++ b/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/Composer.kt
@@ -624,7 +624,11 @@
var canRemove = true
val workDone = observations.forEachScopeOf(value) { scope ->
if (!observationsProcessed.removeValueScope(value, scope)) {
- scope.invalidate()
+ if (scope.invalidate() == InvalidationResult.IGNORED) {
+ // This scope is still in the insert table so we should keep it in
+ // the observation list.
+ canRemove = false
+ }
} else {
canRemove = false
}
diff --git a/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/snapshots/SnapshotFlow.kt b/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/snapshots/SnapshotFlow.kt
new file mode 100644
index 0000000..3ecb3ee
--- /dev/null
+++ b/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/snapshots/SnapshotFlow.kt
@@ -0,0 +1,152 @@
+/*
+ * Copyright 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.compose.runtime.snapshots
+
+import androidx.compose.runtime.ExperimentalComposeApi
+import kotlinx.coroutines.channels.Channel
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.flow
+
+/**
+ * Create a [Flow] from observable [Snapshot] state. (e.g. state holders returned by
+ * [mutableStateOf][androidx.compose.runtime.mutableStateOf].)
+ *
+ * [snapshotFlow] creates a [Flow] that runs [block] when collected and emits the result,
+ * recording any snapshot state that was accessed. While collection continues, if a new [Snapshot]
+ * is applied that changes state accessed by [block], the flow will run [block] again,
+ * re-recording the snapshot state that was accessed.
+ * If the result of [block] is not [equal to][Any.equals] the previous result, the flow will emit
+ * that new result. (This behavior is similar to that of
+ * [Flow.distinctUntilChanged][kotlinx.coroutines.flow.distinctUntilChanged].) Collection will
+ * continue indefinitely unless it is explicitly cancelled or limited by the use of other [Flow]
+ * operators.
+ *
+ * @sample androidx.compose.runtime.samples.snapshotFlowSample
+ *
+ * [block] is run in a **read-only** [Snapshot] and may not modify snapshot data. If [block]
+ * attempts to modify snapshot data, flow collection will fail with [IllegalStateException].
+ *
+ * [block] may run more than once for equal sets of inputs or only once after many rapid
+ * snapshot changes; it should be idempotent and free of side effects.
+ *
+ * When working with [Snapshot] state it is useful to keep the distinction between **events** and
+ * **state** in mind. [snapshotFlow] models snapshot changes as events, but events **cannot** be
+ * effectively modeled as observable state. Observable state is a lossy compression of the events
+ * that produced that state.
+ *
+ * An observable **event** happens at a point in time and is discarded. All registered observers
+ * at the time the event occurred are notified. All individual events in a stream are assumed
+ * to be relevant and may build on one another; repeated equal events have meaning and therefore
+ * a registered observer must observe all events without skipping.
+ *
+ * Observable **state** raises change events when the state changes from one value to a new,
+ * unequal value. State change events are **conflated;** only the most recent state matters.
+ * Observers of state changes must therefore be **idempotent;** given the same state value the
+ * observer should produce the same result. It is valid for a state observer to both skip
+ * intermediate states as well as run multiple times for the same state and the result should
+ * be the same.
+ */
+@ExperimentalComposeApi
+fun <T> snapshotFlow(
+ block: () -> T
+): Flow<T> = flow {
+ // Objects read the last time block was run
+ val readSet = mutableSetOf<Any>()
+ val readObserver: (Any) -> Unit = { readSet.add(it) }
+
+ // This channel may not block or lose data on an offer call.
+ val appliedChanges = Channel<Set<Any>>(Channel.UNLIMITED)
+
+ // Register the apply observer before running for the first time
+ // so that we don't miss updates.
+ val unregisterApplyObserver = Snapshot.registerApplyObserver { changed, _ ->
+ appliedChanges.offer(changed)
+ }
+
+ try {
+ var lastValue = takeSnapshot(readObserver).run {
+ try {
+ enter(block)
+ } finally {
+ dispose()
+ }
+ }
+ emit(lastValue)
+
+ while (true) {
+ var found = false
+ var changedObjects = appliedChanges.receive()
+
+ // Poll for any other changes before running block to minimize the number of
+ // additional times it runs for the same data
+ while (true) {
+ // Assumption: readSet will typically be smaller than changed
+ found = found || readSet.intersects(changedObjects)
+ changedObjects = appliedChanges.poll() ?: break
+ }
+
+ if (found) {
+ readSet.clear()
+ val newValue = takeSnapshot(readObserver).run {
+ try {
+ enter(block)
+ } finally {
+ dispose()
+ }
+ }
+
+ if (newValue != lastValue) {
+ lastValue = newValue
+ emit(newValue)
+ }
+ }
+ }
+ } finally {
+ unregisterApplyObserver()
+ }
+}
+
+/**
+ * Return `true` if there are any elements shared between `this` and [other]
+ */
+private fun <T> Set<T>.intersects(other: Set<T>): Boolean =
+ if (size < other.size) any { it in other } else other.any { it in this }
+
+/**
+ * Take a [MutableSnapshot] and run [block] within it. When [block] returns successfully,
+ * attempt to [MutableSnapshot.apply] the snapshot. Returns the result of [block] or throws
+ * [SnapshotApplyConflictException] if snapshot changes attempted by [block] could not be applied.
+ *
+ * Prior to returning, any changes made to snapshot state (e.g. state holders returned by
+ * [androidx.compose.runtime.mutableStateOf] are not visible to other threads. When
+ * [withMutableSnapshot] returns successfully those changes will be made visible to other threads
+ * and any snapshot observers (e.g. [snapshotFlow]) will be notified of changes.
+ *
+ * [block] must not suspend if [withMutableSnapshot] is called from a suspend function.
+ */
+// TODO: determine a good way to prevent/discourage suspending in an inlined [block]
+@ExperimentalComposeApi
+inline fun <R> withMutableSnapshot(
+ block: () -> R
+): R = takeMutableSnapshot().run {
+ try {
+ enter(block).also { apply().check() }
+ } catch (t: Throwable) {
+ dispose()
+ throw t
+ }
+}
diff --git a/compose/runtime/runtime/src/test/kotlin/androidx/compose/runtime/snapshots/SnapshotFlowTests.kt b/compose/runtime/runtime/src/test/kotlin/androidx/compose/runtime/snapshots/SnapshotFlowTests.kt
new file mode 100644
index 0000000..2bd56c8
--- /dev/null
+++ b/compose/runtime/runtime/src/test/kotlin/androidx/compose/runtime/snapshots/SnapshotFlowTests.kt
@@ -0,0 +1,108 @@
+/*
+ * Copyright 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.compose.runtime.snapshots
+
+import androidx.compose.runtime.ExperimentalComposeApi
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.setValue
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.flow.launchIn
+import kotlinx.coroutines.flow.onEach
+import kotlinx.coroutines.plus
+import kotlinx.coroutines.runBlocking
+import kotlinx.coroutines.yield
+import kotlin.test.Test
+import kotlin.test.assertEquals
+
+@Suppress("RemoveExplicitTypeArguments")
+@OptIn(ExperimentalComposeApi::class)
+class SnapshotFlowTests {
+ @Test
+ fun observeBasicChanges() = runBlocking<Unit> {
+ var state by mutableStateOf(1)
+ var result = 0
+
+ // Use Dispatchers.Unconfined to cause the observer to run immediately for this test,
+ // both here and when we apply a change.
+ val collector = snapshotFlow { state * 2 }
+ .onEach { result = it }
+ .launchIn(this + Dispatchers.Unconfined)
+
+ assertEquals(2, result, "value after initial run")
+
+ withMutableSnapshot {
+ state = 5
+ }
+
+ assertEquals(10, result, "value after snapshot update")
+
+ collector.cancel()
+ }
+
+ @Test
+ fun coalesceChanges() = runBlocking<Unit> {
+ var state by mutableStateOf(1)
+ var runCount = 0
+
+ // This test uses the runBlocking single-threaded dispatcher for observation, which means
+ // we don't flush changes to the observer until we yield() intentionally.
+ val collector = snapshotFlow { state }
+ .onEach { runCount++ }
+ .launchIn(this)
+
+ assertEquals(0, runCount, "initial value - snapshot collector hasn't run yet")
+ yield()
+ assertEquals(1, runCount, "snapshot collector initial run")
+
+ withMutableSnapshot { state++ }
+ yield()
+
+ assertEquals(2, runCount, "made one change")
+
+ withMutableSnapshot { state++ }
+ withMutableSnapshot { state++ }
+ yield()
+
+ assertEquals(3, runCount, "coalesced two changes")
+
+ collector.cancel()
+ }
+
+ @Test
+ fun ignoreUnrelatedChanges() = runBlocking<Unit> {
+ val state by mutableStateOf(1)
+ var unrelatedState by mutableStateOf(1)
+ var runCount = 0
+
+ // This test uses the runBlocking single-threaded dispatcher for observation, which means
+ // we don't flush changes to the observer until we yield() intentionally.
+ val collector = snapshotFlow { state }
+ .onEach { runCount++ }
+ .launchIn(this)
+ yield()
+
+ assertEquals(1, runCount, "initial run")
+
+ withMutableSnapshot { unrelatedState++ }
+ yield()
+
+ assertEquals(1, runCount, "after changing unrelated state")
+
+ collector.cancel()
+ }
+}
diff --git a/compose/ui/ui-geometry/api/current.txt b/compose/ui/ui-geometry/api/current.txt
index 1fb035f..f7afa35 100644
--- a/compose/ui/ui-geometry/api/current.txt
+++ b/compose/ui/ui-geometry/api/current.txt
@@ -1,6 +1,33 @@
// Signature format: 3.0
package androidx.compose.ui.geometry {
+ public final class MutableRect {
+ ctor public MutableRect(float left, float top, float right, float bottom);
+ method public boolean contains-k-4lQ0M(long offset);
+ method public float getBottom();
+ method public inline float getHeight();
+ method public float getLeft();
+ method public float getRight();
+ method public long getSize();
+ method public float getTop();
+ method public inline float getWidth();
+ method @androidx.compose.runtime.Stable public void intersect(float left, float top, float right, float bottom);
+ method public boolean isEmpty();
+ method public void set(float left, float top, float right, float bottom);
+ method public void setBottom(float p);
+ method public void setLeft(float p);
+ method public void setRight(float p);
+ method public void setTop(float p);
+ property public final inline float height;
+ property public final boolean isEmpty;
+ property public final long size;
+ property public final inline float width;
+ }
+
+ public final class MutableRectKt {
+ method public static androidx.compose.ui.geometry.Rect toRect(androidx.compose.ui.geometry.MutableRect);
+ }
+
@androidx.compose.runtime.Immutable public final inline class Offset {
ctor public Offset();
method @androidx.compose.runtime.Stable public static operator float component1-impl(long $this);
@@ -126,10 +153,6 @@
}
public static final class Rect.Companion {
- method @Deprecated @androidx.compose.runtime.Stable public androidx.compose.ui.geometry.Rect fromCircle-MQFEXWE(long center, float radius);
- method @Deprecated @androidx.compose.runtime.Stable public androidx.compose.ui.geometry.Rect fromLTRB(float left, float top, float right, float bottom);
- method @Deprecated @androidx.compose.runtime.Stable public androidx.compose.ui.geometry.Rect fromLTWH(float left, float top, float width, float height);
- method @Deprecated @androidx.compose.runtime.Stable public androidx.compose.ui.geometry.Rect fromPoints-FZVz7gs(long a, long b);
method public androidx.compose.ui.geometry.Rect getZero();
property public final androidx.compose.ui.geometry.Rect Zero;
}
diff --git a/compose/ui/ui-geometry/api/public_plus_experimental_current.txt b/compose/ui/ui-geometry/api/public_plus_experimental_current.txt
index 1fb035f..f7afa35 100644
--- a/compose/ui/ui-geometry/api/public_plus_experimental_current.txt
+++ b/compose/ui/ui-geometry/api/public_plus_experimental_current.txt
@@ -1,6 +1,33 @@
// Signature format: 3.0
package androidx.compose.ui.geometry {
+ public final class MutableRect {
+ ctor public MutableRect(float left, float top, float right, float bottom);
+ method public boolean contains-k-4lQ0M(long offset);
+ method public float getBottom();
+ method public inline float getHeight();
+ method public float getLeft();
+ method public float getRight();
+ method public long getSize();
+ method public float getTop();
+ method public inline float getWidth();
+ method @androidx.compose.runtime.Stable public void intersect(float left, float top, float right, float bottom);
+ method public boolean isEmpty();
+ method public void set(float left, float top, float right, float bottom);
+ method public void setBottom(float p);
+ method public void setLeft(float p);
+ method public void setRight(float p);
+ method public void setTop(float p);
+ property public final inline float height;
+ property public final boolean isEmpty;
+ property public final long size;
+ property public final inline float width;
+ }
+
+ public final class MutableRectKt {
+ method public static androidx.compose.ui.geometry.Rect toRect(androidx.compose.ui.geometry.MutableRect);
+ }
+
@androidx.compose.runtime.Immutable public final inline class Offset {
ctor public Offset();
method @androidx.compose.runtime.Stable public static operator float component1-impl(long $this);
@@ -126,10 +153,6 @@
}
public static final class Rect.Companion {
- method @Deprecated @androidx.compose.runtime.Stable public androidx.compose.ui.geometry.Rect fromCircle-MQFEXWE(long center, float radius);
- method @Deprecated @androidx.compose.runtime.Stable public androidx.compose.ui.geometry.Rect fromLTRB(float left, float top, float right, float bottom);
- method @Deprecated @androidx.compose.runtime.Stable public androidx.compose.ui.geometry.Rect fromLTWH(float left, float top, float width, float height);
- method @Deprecated @androidx.compose.runtime.Stable public androidx.compose.ui.geometry.Rect fromPoints-FZVz7gs(long a, long b);
method public androidx.compose.ui.geometry.Rect getZero();
property public final androidx.compose.ui.geometry.Rect Zero;
}
diff --git a/compose/ui/ui-geometry/api/restricted_current.txt b/compose/ui/ui-geometry/api/restricted_current.txt
index 1fb035f..f7afa35 100644
--- a/compose/ui/ui-geometry/api/restricted_current.txt
+++ b/compose/ui/ui-geometry/api/restricted_current.txt
@@ -1,6 +1,33 @@
// Signature format: 3.0
package androidx.compose.ui.geometry {
+ public final class MutableRect {
+ ctor public MutableRect(float left, float top, float right, float bottom);
+ method public boolean contains-k-4lQ0M(long offset);
+ method public float getBottom();
+ method public inline float getHeight();
+ method public float getLeft();
+ method public float getRight();
+ method public long getSize();
+ method public float getTop();
+ method public inline float getWidth();
+ method @androidx.compose.runtime.Stable public void intersect(float left, float top, float right, float bottom);
+ method public boolean isEmpty();
+ method public void set(float left, float top, float right, float bottom);
+ method public void setBottom(float p);
+ method public void setLeft(float p);
+ method public void setRight(float p);
+ method public void setTop(float p);
+ property public final inline float height;
+ property public final boolean isEmpty;
+ property public final long size;
+ property public final inline float width;
+ }
+
+ public final class MutableRectKt {
+ method public static androidx.compose.ui.geometry.Rect toRect(androidx.compose.ui.geometry.MutableRect);
+ }
+
@androidx.compose.runtime.Immutable public final inline class Offset {
ctor public Offset();
method @androidx.compose.runtime.Stable public static operator float component1-impl(long $this);
@@ -126,10 +153,6 @@
}
public static final class Rect.Companion {
- method @Deprecated @androidx.compose.runtime.Stable public androidx.compose.ui.geometry.Rect fromCircle-MQFEXWE(long center, float radius);
- method @Deprecated @androidx.compose.runtime.Stable public androidx.compose.ui.geometry.Rect fromLTRB(float left, float top, float right, float bottom);
- method @Deprecated @androidx.compose.runtime.Stable public androidx.compose.ui.geometry.Rect fromLTWH(float left, float top, float width, float height);
- method @Deprecated @androidx.compose.runtime.Stable public androidx.compose.ui.geometry.Rect fromPoints-FZVz7gs(long a, long b);
method public androidx.compose.ui.geometry.Rect getZero();
property public final androidx.compose.ui.geometry.Rect Zero;
}
diff --git a/compose/ui/ui-geometry/build.gradle b/compose/ui/ui-geometry/build.gradle
index 36affd2..25f65c1 100644
--- a/compose/ui/ui-geometry/build.gradle
+++ b/compose/ui/ui-geometry/build.gradle
@@ -55,7 +55,7 @@
androidx {
name = "Compose Geometry"
publish = Publish.SNAPSHOT_AND_RELEASE
- mavenVersion = LibraryVersions.UI
+ mavenVersion = LibraryVersions.COMPOSE
mavenGroup = LibraryGroups.Compose.UI
inceptionYear = "2020"
description = "Compose classes related to dimensions without units"
diff --git a/compose/ui/ui-geometry/src/commonMain/kotlin/androidx/compose/ui/geometry/MutableRect.kt b/compose/ui/ui-geometry/src/commonMain/kotlin/androidx/compose/ui/geometry/MutableRect.kt
new file mode 100644
index 0000000..9d3ee8f
--- /dev/null
+++ b/compose/ui/ui-geometry/src/commonMain/kotlin/androidx/compose/ui/geometry/MutableRect.kt
@@ -0,0 +1,102 @@
+/*
+ * Copyright 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.compose.ui.geometry
+
+import androidx.compose.runtime.Stable
+import androidx.compose.ui.util.toStringAsFixed
+import kotlin.math.max
+import kotlin.math.min
+
+/**
+ * An mutable, 2D, axis-aligned, floating-point rectangle whose coordinates
+ * are relative to a given origin.
+ *
+ * @param left The offset of the left edge of this rectangle from the x axis.
+ * @param top The offset of the top edge of this rectangle from the y axis.
+ * @param right The offset of the right edge of this rectangle from the x axis.
+ * @param bottom The offset of the bottom edge of this rectangle from the y axis.
+ */
+class MutableRect(
+ var left: Float,
+ var top: Float,
+ var right: Float,
+ var bottom: Float
+) {
+ /** The distance between the left and right edges of this rectangle. */
+ inline val width: Float
+ get() = right - left
+
+ /** The distance between the top and bottom edges of this rectangle. */
+ inline val height: Float
+ get() = bottom - top
+
+ /**
+ * The distance between the upper-left corner and the lower-right corner of
+ * this rectangle.
+ */
+ val size: Size
+ get() = Size(width, height)
+
+ /**
+ * Whether this rectangle encloses a non-zero area. Negative areas are
+ * considered empty.
+ */
+ val isEmpty: Boolean
+ get() = left >= right || top >= bottom
+
+ /**
+ * Modifies `this` to be the intersection of this and the rect formed
+ * by [left], [top], [right], and [bottom].
+ */
+ @Stable
+ fun intersect(left: Float, top: Float, right: Float, bottom: Float) {
+ this.left = max(left, this.left)
+ this.top = max(top, this.top)
+ this.right = min(right, this.right)
+ this.bottom = min(bottom, this.bottom)
+ }
+
+ /**
+ * Whether the point specified by the given offset (which is assumed to be
+ * relative to the origin) lies between the left and right and the top and
+ * bottom edges of this rectangle.
+ *
+ * Rectangles include their top and left edges but exclude their bottom and
+ * right edges.
+ */
+ fun contains(offset: Offset): Boolean {
+ return offset.x >= left && offset.x < right && offset.y >= top && offset.y < bottom
+ }
+
+ /**
+ * Sets new bounds to ([left], [top], [right], [bottom])
+ */
+ fun set(left: Float, top: Float, right: Float, bottom: Float) {
+ this.left = left
+ this.top = top
+ this.right = right
+ this.bottom = bottom
+ }
+
+ override fun toString() = "MutableRect(" +
+ "${left.toStringAsFixed(1)}, " +
+ "${top.toStringAsFixed(1)}, " +
+ "${right.toStringAsFixed(1)}, " +
+ "${bottom.toStringAsFixed(1)})"
+}
+
+fun MutableRect.toRect(): Rect = Rect(left, top, right, bottom)
\ No newline at end of file
diff --git a/compose/ui/ui-geometry/src/commonMain/kotlin/androidx/compose/ui/geometry/Rect.kt b/compose/ui/ui-geometry/src/commonMain/kotlin/androidx/compose/ui/geometry/Rect.kt
index 19c3570..409b0e1 100644
--- a/compose/ui/ui-geometry/src/commonMain/kotlin/androidx/compose/ui/geometry/Rect.kt
+++ b/compose/ui/ui-geometry/src/commonMain/kotlin/androidx/compose/ui/geometry/Rect.kt
@@ -53,61 +53,6 @@
) {
companion object {
- /** Construct a rectangle from its left, top, right, and bottom edges. */
- @Deprecated("Use Rect constructor instead", ReplaceWith("Rect(left, top, right, bottom)",
- "androidx.compose.ui.geometry"))
- @Stable
- fun fromLTRB(left: Float, top: Float, right: Float, bottom: Float): Rect {
- return Rect(left, top, right, bottom)
- }
-
- /**
- * Construct a rectangle from its left and top edges, its width, and its
- * height.
- *
- * To construct a [Rect] from an [Offset] and a [Size], you can use the
- * rectangle constructor operator `&`. See [Offset.&].
- */
- @Deprecated("Use Rect(Offset, Size) instead",
- ReplaceWith("Rect(Offset(left, top), Size(width, height))",
- "androidx.compose.ui.geometry"))
- @Stable
- fun fromLTWH(left: Float, top: Float, width: Float, height: Float): Rect {
- return Rect(left, top, left + width, top + height)
- }
-
- /**
- * Construct a rectangle that bounds the given circle.
- *
- * The `center` argument is assumed to be an offset from the origin.
- */
- @Deprecated("Use Rect(Offset, Float) instead",
- ReplaceWith("Rect(center, radius)", "androidx.compose.ui.geometry"))
- @Stable
- fun fromCircle(center: Offset, radius: Float): Rect {
- return Rect(
- center.x - radius,
- center.y - radius,
- center.x + radius,
- center.y + radius
- )
- }
-
- /**
- * Construct the smallest rectangle that encloses the given offsets, treating
- * them as vectors from the origin.
- */
- @Stable
- @Deprecated("Use Rect(a, b) instead",
- ReplaceWith("Rect(a, b)", "androidx.compose.ui.geometry"))
- fun fromPoints(a: Offset, b: Offset): Rect {
- return Rect(
- min(a.x, b.x),
- min(a.y, b.y),
- max(a.x, b.x),
- max(a.y, b.y)
- )
- }
/** A rectangle with left, top, right, and bottom edges all at zero. */
@Stable
@@ -392,8 +337,8 @@
fun Rect(center: Offset, radius: Float): Rect =
Rect(
center.x - radius,
- center.x + radius,
center.y - radius,
+ center.x + radius,
center.y + radius
)
diff --git a/compose/ui/ui-geometry/src/test/kotlin/androidx/compose/ui/geometry/MutableRectTest.kt b/compose/ui/ui-geometry/src/test/kotlin/androidx/compose/ui/geometry/MutableRectTest.kt
new file mode 100644
index 0000000..1750492
--- /dev/null
+++ b/compose/ui/ui-geometry/src/test/kotlin/androidx/compose/ui/geometry/MutableRectTest.kt
@@ -0,0 +1,94 @@
+/*
+ * 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.compose.ui.geometry
+
+import org.junit.Assert.assertEquals
+import org.junit.Assert.assertFalse
+import org.junit.Assert.assertTrue
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+
+@RunWith(JUnit4::class)
+class MutableRectTest {
+ @Test
+ fun accessors() {
+ val r = MutableRect(1f, 3f, 5f, 9f)
+ assertEquals(1f, r.left, 0f)
+ assertEquals(3f, r.top, 0f)
+ assertEquals(5f, r.right, 0f)
+ assertEquals(9f, r.bottom, 0f)
+ assertEquals(4f, r.width, 0f)
+ assertEquals(6f, r.height, 0f)
+ assertEquals(Size(4f, 6f), r.size)
+ }
+
+ @Test
+ fun empty() {
+ val r = MutableRect(1f, 3f, 5f, 9f)
+ assertFalse(r.isEmpty)
+ r.left = 5f
+ assertTrue(r.isEmpty)
+ r.left = 1f
+ r.bottom = 3f
+ assertTrue(r.isEmpty)
+ }
+
+ @Test
+ fun contains() {
+ val r = MutableRect(1f, 3f, 5f, 9f)
+ assertTrue(r.contains(Offset(1f, 3f)))
+ assertTrue(r.contains(Offset(3f, 3f)))
+ assertFalse(r.contains(Offset(5f, 3f)))
+ assertTrue(r.contains(Offset(1f, 6f)))
+ assertTrue(r.contains(Offset(3f, 6f)))
+ assertFalse(r.contains(Offset(5f, 6f)))
+ assertFalse(r.contains(Offset(1f, 9f)))
+ assertFalse(r.contains(Offset(3f, 9f)))
+ assertFalse(r.contains(Offset(5f, 9f)))
+ assertFalse(r.contains(Offset(0f, 0f)))
+ assertFalse(r.contains(Offset(Float.POSITIVE_INFINITY, Float.POSITIVE_INFINITY)))
+ assertFalse(r.contains(Offset(Float.NEGATIVE_INFINITY, Float.NEGATIVE_INFINITY)))
+ }
+
+ @Test
+ fun intersect() {
+ val r = MutableRect(0f, 0f, 100f, 100f)
+ r.intersect(50f, 50f, 200f, 200f)
+ assertEquals(50f, r.left, 0f)
+ assertEquals(50f, r.top, 0f)
+ assertEquals(100f, r.right, 0f)
+ assertEquals(100f, r.bottom, 0f)
+
+ val r2 = MutableRect(50f, 50f, 200f, 200f)
+ r2.intersect(0f, 0f, 100f, 100f)
+ assertEquals(50f, r2.left, 0f)
+ assertEquals(50f, r2.top, 0f)
+ assertEquals(100f, r2.right, 0f)
+ assertEquals(100f, r2.bottom, 0f)
+ }
+
+ @Test
+ fun set() {
+ val r = MutableRect(0f, 0f, 100f, 100f)
+ r.set(10f, 3f, 20f, 6f)
+ assertEquals(10f, r.left, 0f)
+ assertEquals(3f, r.top, 0f)
+ assertEquals(20f, r.right, 0f)
+ assertEquals(6f, r.bottom, 0f)
+ }
+}
\ No newline at end of file
diff --git a/compose/ui/ui-geometry/src/test/kotlin/androidx/compose/ui/geometry/RectTest.kt b/compose/ui/ui-geometry/src/test/kotlin/androidx/compose/ui/geometry/RectTest.kt
index 79a7703..5edcabaf 100644
--- a/compose/ui/ui-geometry/src/test/kotlin/androidx/compose/ui/geometry/RectTest.kt
+++ b/compose/ui/ui-geometry/src/test/kotlin/androidx/compose/ui/geometry/RectTest.kt
@@ -17,9 +17,11 @@
package androidx.compose.ui.geometry
import org.junit.Assert.assertEquals
+import org.junit.Assert.assertFalse
import org.junit.Test
import org.junit.runner.RunWith
import org.junit.runners.JUnit4
+import kotlin.test.assertTrue
@RunWith(JUnit4::class)
class RectTest {
@@ -58,4 +60,220 @@
val r4 = r2.intersect(r1)
assertEquals(r3, r4)
}
+
+ @Test
+ fun `rect width`() {
+ assertEquals(210f, Rect(70f, 10f, 280f, 300f).width)
+ }
+
+ @Test
+ fun `rect height`() {
+ assertEquals(290f, Rect(70f, 10f, 280f, 300f).height)
+ }
+
+ @Test
+ fun `rect size`() {
+ assertEquals(Size(210f, 290f),
+ Rect(70f, 10f, 280f, 300f).size)
+ }
+
+ @Test
+ fun `rect infinite`() {
+ assertTrue(Rect(Float.POSITIVE_INFINITY, 10f, 200f, 500f).isInfinite)
+ assertTrue(Rect(10f, Float.POSITIVE_INFINITY, 200f, 500f).isInfinite)
+ assertTrue(Rect(10f, 200f, Float.POSITIVE_INFINITY, 500f).isInfinite)
+ assertTrue(Rect(10f, 200f, 500f, Float.POSITIVE_INFINITY).isInfinite)
+
+ assertFalse(Rect(0f, 1f, 2f, 3f).isInfinite)
+ }
+
+ @Test
+ fun `rect finite`() {
+ assertTrue(Rect(0f, 1f, 2f, 3f).isFinite)
+ assertFalse(Rect(0f, 1f, 2f, Float.POSITIVE_INFINITY).isFinite)
+ }
+
+ @Test
+ fun `rect isEmpty`() {
+ assertTrue(Rect(0f, 0f, 0f, 10f).isEmpty)
+ assertTrue(Rect(1f, 0f, 0f, 10f).isEmpty)
+ assertTrue(Rect(0f, 1f, 10f, 0f).isEmpty)
+ assertTrue(Rect(0f, 1f, 10f, 1f).isEmpty)
+
+ assertFalse(Rect(0f, 1f, 2f, 3f).isEmpty)
+ }
+
+ @Test
+ fun `rect shift`() {
+ val shifted = Rect(0f, 5f, 10f, 15f).shift(Offset(10f, 15f))
+ assertEquals(Rect(10f, 20f, 20f, 30f), shifted)
+ }
+
+ @Test
+ fun `rect translate`() {
+ val translated = Rect(0f, 5f, 10f, 15f).translate(10f, 15f)
+ assertEquals(Rect(10f, 20f, 20f, 30f), translated)
+ }
+
+ @Test
+ fun `rect inflate`() {
+ val inflated = Rect(5f, 10f, 10f, 20f).inflate(5f)
+ assertEquals(Rect(0f, 5f, 15f, 25f), inflated)
+ }
+
+ @Test
+ fun `rect deflate`() {
+ val deflated = Rect(0f, 5f, 15f, 25f).deflate(5f)
+ assertEquals(Rect(5f, 10f, 10f, 20f), deflated)
+ }
+
+ @Test
+ fun `rect intersect`() {
+ val intersected = Rect(0f, 0f, 20f, 20f).intersect(
+ Rect(10f, 10f, 30f, 30f))
+ assertEquals(Rect(10f, 10f, 20f, 20f), intersected)
+ }
+
+ @Test
+ fun `rect expandToInclude`() {
+ val expanded = Rect(0f, 0f, 20f, 20f).expandToInclude(
+ Rect(10f, 10f, 30f, 30f))
+ assertEquals(Rect(0f, 0f, 30f, 30f), expanded)
+ }
+
+ @Test
+ fun `rect join`() {
+ val rect1 = Rect.Zero
+ val rect2 = Rect(12f, 24f, 64f, 128f)
+ assertEquals(rect2, rect2.join(rect1))
+ assertEquals(rect2, rect1.join(rect2))
+ }
+
+ @Test
+ fun `rect overlap`() {
+ val rect1 = Rect(0f, 5f, 10f, 15f)
+ val rect2 = Rect(5f, 10f, 15f, 20f)
+ assertTrue(rect1.overlaps(rect2))
+ assertTrue(rect2.overlaps(rect1))
+ }
+
+ @Test
+ fun `rect does not overlap`() {
+ val rect1 = Rect(0f, 5f, 10f, 15f)
+ val rect2 = Rect(10f, 5f, 20f, 15f)
+ assertFalse(rect1.overlaps(rect2))
+ assertFalse(rect2.overlaps(rect1))
+ }
+
+ @Test
+ fun `rect minDimension`() {
+ val rect = Rect(0f, 5f, 100f, 25f)
+ assertEquals(20f, rect.minDimension)
+ }
+
+ @Test
+ fun `rect maxDimension`() {
+ val rect = Rect(0f, 5f, 100f, 25f)
+ assertEquals(100f, rect.maxDimension)
+ }
+
+ @Test
+ fun `rect topLeft`() {
+ val rect = Rect(27f, 38f, 100f, 200f)
+ assertEquals(Offset(27f, 38f), rect.topLeft)
+ }
+
+ @Test
+ fun `rect topCenter`() {
+ val rect = Rect(100f, 15f, 200f, 300f)
+ assertEquals(Offset(150f, 15f), rect.topCenter)
+ }
+
+ @Test
+ fun `rect topRight`() {
+ val rect = Rect(100f, 15f, 200f, 300f)
+ assertEquals(Offset(200f, 15f), rect.topRight)
+ }
+
+ @Test
+ fun `rect centerLeft`() {
+ val rect = Rect(100f, 10f, 200f, 300f)
+ assertEquals(Offset(100f, 155f), rect.centerLeft)
+ }
+
+ @Test
+ fun `rect center`() {
+ val rect = Rect(100f, 10f, 200f, 300f)
+ assertEquals(Offset(150f, 155f), rect.center)
+ }
+
+ @Test
+ fun `rect centerRight`() {
+ val rect = Rect(100f, 10f, 200f, 300f)
+ assertEquals(Offset(200f, 155f), rect.centerRight)
+ }
+
+ @Test
+ fun `rect bottomLeft`() {
+ val rect = Rect(100f, 10f, 200f, 300f)
+ assertEquals(Offset(100f, 300f), rect.bottomLeft)
+ }
+
+ @Test
+ fun `rect bottomCenter`() {
+ val rect = Rect(100f, 10f, 200f, 300f)
+ assertEquals(Offset(150f, 300f), rect.bottomCenter)
+ }
+
+ @Test
+ fun `rect bottomRight`() {
+ val rect = Rect(100f, 10f, 200f, 300f)
+ assertEquals(Offset(200f, 300f), rect.bottomRight)
+ }
+
+ @Test
+ fun `rect contains`() {
+ val rect = Rect(100f, 10f, 200f, 300f)
+ val offset = Offset(177f, 288f)
+ assertTrue(rect.contains(offset))
+ }
+
+ @Test
+ fun `rect does not contain`() {
+ val rect = Rect(100f, 10f, 200f, 300f)
+ val offset1 = Offset(201f, 150f)
+ assertFalse(rect.contains(offset1))
+
+ val offset2 = Offset(200f, 301f)
+ assertFalse(rect.contains(offset2))
+ }
+
+ @Test
+ fun `rect from offset and size`() {
+ val offset = Offset(220f, 300f)
+ val size = Size(80f, 200f)
+ assertEquals(Rect(220f, 300f, 300f, 500f), Rect(offset, size))
+ }
+
+ @Test
+ fun `rect from topleft and bottomRight`() {
+ val offset1 = Offset(27f, 38f)
+ val offset2 = Offset(130f, 280f)
+ assertEquals(Rect(27f, 38f, 130f, 280f), Rect(offset1, offset2))
+ }
+
+ @Test
+ fun `rect from center and radius`() {
+ val offset = Offset(100f, 50f)
+ val radius = 25f
+ assertEquals(Rect(75f, 25f, 125f, 75f), Rect(offset, radius))
+ }
+
+ @Test
+ fun `rect lerp`() {
+ val rect1 = Rect(0f, 0f, 100f, 100f)
+ val rect2 = Rect(50f, 50f, 200f, 200f)
+
+ assertEquals(Rect(25f, 25f, 150f, 150f), lerp(rect1, rect2, 0.5f))
+ }
}
\ No newline at end of file
diff --git a/compose/ui/ui-graphics/api/api_lint.ignore b/compose/ui/ui-graphics/api/api_lint.ignore
index ab64290..b984c1b 100644
--- a/compose/ui/ui-graphics/api/api_lint.ignore
+++ b/compose/ui/ui-graphics/api/api_lint.ignore
@@ -9,14 +9,6 @@
Method parameter should be Collection<Pair> (or subclass) instead of raw array; was `kotlin.Pair<java.lang.Float,androidx.compose.ui.graphics.Color>[]`
-AutoBoxing: androidx.compose.ui.graphics.vectormath.Matrix4#scale(Object, Float, Float) parameter #1:
- Must avoid boxed primitives (`java.lang.Float`)
-AutoBoxing: androidx.compose.ui.graphics.vectormath.Matrix4#scale(Object, Float, Float) parameter #2:
- Must avoid boxed primitives (`java.lang.Float`)
-AutoBoxing: androidx.compose.ui.graphics.vectormath.Matrix4Kt#getAsScale(androidx.compose.ui.graphics.vectormath.Matrix4):
- Must avoid boxed primitives (`java.lang.Float`)
-
-
BuilderSetStyle: androidx.compose.ui.graphics.vector.PathBuilder#arcTo(float, float, float, boolean, boolean, float, float):
Builder methods names should use setFoo() / addFoo() / clearFoo() style: method androidx.compose.ui.graphics.vector.PathBuilder.arcTo(float,float,float,boolean,boolean,float,float)
BuilderSetStyle: androidx.compose.ui.graphics.vector.PathBuilder#arcToRelative(float, float, float, boolean, boolean, float, float):
@@ -57,38 +49,24 @@
Builder methods names should use setFoo() / addFoo() / clearFoo() style: method androidx.compose.ui.graphics.vector.PathBuilder.verticalLineToRelative(float)
-DocumentExceptions: androidx.compose.ui.graphics.vectormath.Matrix3#get(androidx.compose.ui.graphics.vectormath.MatrixColumn):
- Method Matrix3.get appears to be throwing java.lang.IllegalArgumentException; this should be listed in the documentation; see https://android.github.io/kotlin-guides/interop.html#document-exceptions
-DocumentExceptions: androidx.compose.ui.graphics.vectormath.Matrix3#get(int):
- Method Matrix3.get appears to be throwing java.lang.IllegalArgumentException; this should be listed in the documentation; see https://android.github.io/kotlin-guides/interop.html#document-exceptions
-DocumentExceptions: androidx.compose.ui.graphics.vectormath.Matrix4#get(int):
- Method Matrix4.get appears to be throwing java.lang.IllegalArgumentException; this should be listed in the documentation; see https://android.github.io/kotlin-guides/interop.html#document-exceptions
-DocumentExceptions: androidx.compose.ui.graphics.vectormath.Vector2#get(androidx.compose.ui.graphics.vectormath.VectorComponent):
- Method Vector2.get appears to be throwing java.lang.IllegalArgumentException; this should be listed in the documentation; see https://android.github.io/kotlin-guides/interop.html#document-exceptions
-DocumentExceptions: androidx.compose.ui.graphics.vectormath.Vector2#get(int):
- Method Vector2.get appears to be throwing java.lang.IllegalArgumentException; this should be listed in the documentation; see https://android.github.io/kotlin-guides/interop.html#document-exceptions
-DocumentExceptions: androidx.compose.ui.graphics.vectormath.Vector2#set(androidx.compose.ui.graphics.vectormath.VectorComponent, float):
- Method Vector2.set appears to be throwing java.lang.IllegalArgumentException; this should be listed in the documentation; see https://android.github.io/kotlin-guides/interop.html#document-exceptions
-DocumentExceptions: androidx.compose.ui.graphics.vectormath.Vector2#set(int, float):
- Method Vector2.set appears to be throwing java.lang.IllegalArgumentException; this should be listed in the documentation; see https://android.github.io/kotlin-guides/interop.html#document-exceptions
-DocumentExceptions: androidx.compose.ui.graphics.vectormath.Vector3#get(androidx.compose.ui.graphics.vectormath.VectorComponent):
- Method Vector3.get appears to be throwing java.lang.IllegalArgumentException; this should be listed in the documentation; see https://android.github.io/kotlin-guides/interop.html#document-exceptions
-DocumentExceptions: androidx.compose.ui.graphics.vectormath.Vector3#get(int):
- Method Vector3.get appears to be throwing java.lang.IllegalArgumentException; this should be listed in the documentation; see https://android.github.io/kotlin-guides/interop.html#document-exceptions
-DocumentExceptions: androidx.compose.ui.graphics.vectormath.Vector3#set(androidx.compose.ui.graphics.vectormath.VectorComponent, float):
- Method Vector3.set appears to be throwing java.lang.IllegalArgumentException; this should be listed in the documentation; see https://android.github.io/kotlin-guides/interop.html#document-exceptions
-DocumentExceptions: androidx.compose.ui.graphics.vectormath.Vector3#set(int, float):
- Method Vector3.set appears to be throwing java.lang.IllegalArgumentException; this should be listed in the documentation; see https://android.github.io/kotlin-guides/interop.html#document-exceptions
-DocumentExceptions: androidx.compose.ui.graphics.vectormath.Vector4#get(int):
- Method Vector4.get appears to be throwing java.lang.IllegalArgumentException; this should be listed in the documentation; see https://android.github.io/kotlin-guides/interop.html#document-exceptions
-DocumentExceptions: androidx.compose.ui.graphics.vectormath.Vector4#set(int, float):
- Method Vector4.set appears to be throwing java.lang.IllegalArgumentException; this should be listed in the documentation; see https://android.github.io/kotlin-guides/interop.html#document-exceptions
-
-
MissingBuildMethod: androidx.compose.ui.graphics.vector.PathBuilder:
androidx.compose.ui.graphics.vector.PathBuilder does not declare a `build()` method, but builder classes are expected to
+MissingNullability: androidx.compose.ui.graphics.Matrix#constructor-impl(float[]):
+ Missing nullability on method `constructor-impl` return
+MissingNullability: androidx.compose.ui.graphics.Matrix#equals-impl(float[], Object) parameter #0:
+ Missing nullability on parameter `p` in method `equals-impl`
+MissingNullability: androidx.compose.ui.graphics.Matrix#hashCode-impl(float[]) parameter #0:
+ Missing nullability on parameter `p` in method `hashCode-impl`
+MissingNullability: androidx.compose.ui.graphics.Matrix#invert-impl(float[]) parameter #0:
+ Missing nullability on parameter `$this` in method `invert-impl`
+MissingNullability: androidx.compose.ui.graphics.Matrix#reset-impl(float[]) parameter #0:
+ Missing nullability on parameter `$this` in method `reset-impl`
+MissingNullability: androidx.compose.ui.graphics.Matrix#toString-impl(float[]) parameter #0:
+ Missing nullability on parameter `$this` in method `toString-impl`
+
+
NotCloseable: androidx.compose.ui.graphics.AndroidPath:
Classes that release resources (close()) should implement AutoClosable and CloseGuard: class androidx.compose.ui.graphics.AndroidPath
NotCloseable: androidx.compose.ui.graphics.Path:
diff --git a/compose/ui/ui-graphics/api/current.txt b/compose/ui/ui-graphics/api/current.txt
index 2e6e854..9931c09 100644
--- a/compose/ui/ui-graphics/api/current.txt
+++ b/compose/ui/ui-graphics/api/current.txt
@@ -16,6 +16,11 @@
method public static androidx.compose.ui.graphics.ImageAsset imageFromResource(android.content.res.Resources res, int resId);
}
+ public final class AndroidMatrixConversionsKt {
+ method public static void setFrom-7lL006A(float[], android.graphics.Matrix matrix);
+ method public static void setFrom-8AuSnpc(android.graphics.Matrix, float[] matrix);
+ }
+
public final class AndroidPaint implements androidx.compose.ui.graphics.Paint {
ctor public AndroidPaint();
method public android.graphics.Paint asFrameworkPaint();
@@ -178,7 +183,7 @@
method public void clipPath(androidx.compose.ui.graphics.Path path, androidx.compose.ui.graphics.ClipOp clipOp = androidx.compose.ui.graphics.ClipOp.Intersect);
method public default void clipRect(androidx.compose.ui.geometry.Rect rect, androidx.compose.ui.graphics.ClipOp clipOp = androidx.compose.ui.graphics.ClipOp.Intersect);
method public void clipRect(float left, float top, float right, float bottom, androidx.compose.ui.graphics.ClipOp clipOp = androidx.compose.ui.graphics.ClipOp.Intersect);
- method public void concat(androidx.compose.ui.graphics.vectormath.Matrix4 matrix4);
+ method public void concat-58bKbWc(float[] matrix);
method public void disableZ();
method public default void drawArc(androidx.compose.ui.geometry.Rect rect, float startAngle, float sweepAngle, boolean useCenter, androidx.compose.ui.graphics.Paint paint);
method public void drawArc(float left, float top, float right, float bottom, float startAngle, float sweepAngle, boolean useCenter, androidx.compose.ui.graphics.Paint paint);
@@ -307,6 +312,9 @@
method public static inline long useOrElse-iYUlWp8(long, kotlin.jvm.functions.Function0<androidx.compose.ui.graphics.Color> block);
}
+ public final class DegreesKt {
+ }
+
public enum FilterQuality {
method public static androidx.compose.ui.graphics.FilterQuality valueOf(String name) throws java.lang.IllegalArgumentException;
method public static androidx.compose.ui.graphics.FilterQuality[] values();
@@ -353,6 +361,34 @@
method @androidx.compose.runtime.Immutable public androidx.compose.ui.graphics.LinearGradient copy(java.util.List<androidx.compose.ui.graphics.Color> colors, java.util.List<java.lang.Float>? stops, float startX, float startY, float endX, float endY, androidx.compose.ui.graphics.TileMode tileMode);
}
+ public final inline class Matrix {
+ ctor public Matrix();
+ method public static float[]! constructor-impl(float[] values);
+ method public static inline boolean equals-impl(float[]! p, Object? p1);
+ method public static boolean equals-impl0(float[] p1, float[] p2);
+ method public static inline operator float get-impl(float[] $this, int row, int column);
+ method public float[] getValues();
+ method public static inline int hashCode-impl(float[]! p);
+ method public static void invert-impl(float[]! $this);
+ method public static androidx.compose.ui.geometry.Rect map-impl(float[] $this, androidx.compose.ui.geometry.Rect rect);
+ method public static void map-impl(float[] $this, androidx.compose.ui.geometry.MutableRect rect);
+ method public static long map-k-4lQ0M(float[] $this, long point);
+ method public static void reset-impl(float[]! $this);
+ method public static void rotateX-impl(float[] $this, float degrees);
+ method public static void rotateY-impl(float[] $this, float degrees);
+ method public static void rotateZ-impl(float[] $this, float degrees);
+ method public static void scale-impl(float[] $this, float x = 1f, float y = 1f, float z = 1f);
+ method public static inline operator void set-impl(float[] $this, int row, int column, float v);
+ method public static void setFrom-58bKbWc(float[] $this, float[] matrix);
+ method public static operator void timesAssign-58bKbWc(float[] $this, float[] m);
+ method public static String toString-impl(float[]! $this);
+ method public static void translate-impl(float[] $this, float x = 0f, float y = 0f, float z = 0f);
+ }
+
+ public final class MatrixKt {
+ method public static boolean isIdentity-58bKbWc(float[]);
+ }
+
public abstract sealed class Outline {
}
@@ -868,7 +904,7 @@
method public void inset(float left, float top, float right, float bottom);
method public void rotate(float degrees, float pivotX = center.x, float pivotY = center.y);
method public void scale(float scaleX, float scaleY = scaleX, float pivotX = center.x, float pivotY = center.y);
- method public void transform(androidx.compose.ui.graphics.vectormath.Matrix4 matrix);
+ method public void transform-58bKbWc(float[] matrix);
method public void translate(float left = 0.0f, float top = 0.0f);
property public default long center;
property public abstract long size;
@@ -1197,512 +1233,3 @@
}
-package androidx.compose.ui.graphics.vectormath {
-
- public final class Matrix3 {
- ctor public Matrix3(androidx.compose.ui.graphics.vectormath.Vector3 x, androidx.compose.ui.graphics.vectormath.Vector3 y, androidx.compose.ui.graphics.vectormath.Vector3 z);
- ctor public Matrix3();
- ctor public Matrix3(androidx.compose.ui.graphics.vectormath.Matrix3 m);
- method public androidx.compose.ui.graphics.vectormath.Vector3 component1();
- method public androidx.compose.ui.graphics.vectormath.Vector3 component2();
- method public androidx.compose.ui.graphics.vectormath.Vector3 component3();
- method public androidx.compose.ui.graphics.vectormath.Matrix3 copy(androidx.compose.ui.graphics.vectormath.Vector3 x, androidx.compose.ui.graphics.vectormath.Vector3 y, androidx.compose.ui.graphics.vectormath.Vector3 z);
- method public operator androidx.compose.ui.graphics.vectormath.Matrix3 dec();
- method public operator androidx.compose.ui.graphics.vectormath.Matrix3 div(float v);
- method public operator androidx.compose.ui.graphics.vectormath.Vector3 get(int column);
- method public operator float get(int column, int row);
- method public operator androidx.compose.ui.graphics.vectormath.Vector3 get(androidx.compose.ui.graphics.vectormath.MatrixColumn column);
- method public operator float get(androidx.compose.ui.graphics.vectormath.MatrixColumn column, int row);
- method public inline java.util.List<java.lang.Float> getM3storage();
- method public androidx.compose.ui.graphics.vectormath.Vector3 getX();
- method public androidx.compose.ui.graphics.vectormath.Vector3 getY();
- method public androidx.compose.ui.graphics.vectormath.Vector3 getZ();
- method public operator androidx.compose.ui.graphics.vectormath.Matrix3 inc();
- method public operator androidx.compose.ui.graphics.vectormath.Matrix3 minus(float v);
- method public operator androidx.compose.ui.graphics.vectormath.Matrix3 plus(float v);
- method public operator void set(int column, androidx.compose.ui.graphics.vectormath.Vector3 v);
- method public operator void set(int column, int row, float v);
- method public void setX(androidx.compose.ui.graphics.vectormath.Vector3 p);
- method public void setY(androidx.compose.ui.graphics.vectormath.Vector3 p);
- method public void setZ(androidx.compose.ui.graphics.vectormath.Vector3 p);
- method public operator androidx.compose.ui.graphics.vectormath.Matrix3 times(float v);
- method public operator androidx.compose.ui.graphics.vectormath.Matrix3 times(androidx.compose.ui.graphics.vectormath.Matrix3 m);
- method public operator androidx.compose.ui.graphics.vectormath.Vector3 times(androidx.compose.ui.graphics.vectormath.Vector3 v);
- method public float[] toFloatArray();
- method public operator androidx.compose.ui.graphics.vectormath.Matrix3 unaryMinus();
- property public final inline java.util.List<java.lang.Float> m3storage;
- field public static final androidx.compose.ui.graphics.vectormath.Matrix3.Companion Companion;
- }
-
- public static final class Matrix3.Companion {
- method public androidx.compose.ui.graphics.vectormath.Matrix3 identity();
- method public androidx.compose.ui.graphics.vectormath.Matrix3 of(float... a);
- }
-
- public final class Matrix4 {
- ctor public Matrix4(androidx.compose.ui.graphics.vectormath.Vector4 x, androidx.compose.ui.graphics.vectormath.Vector4 y, androidx.compose.ui.graphics.vectormath.Vector4 z, androidx.compose.ui.graphics.vectormath.Vector4 w);
- ctor public Matrix4();
- ctor public Matrix4(androidx.compose.ui.graphics.vectormath.Vector3 right, androidx.compose.ui.graphics.vectormath.Vector3 up, androidx.compose.ui.graphics.vectormath.Vector3 forward, androidx.compose.ui.graphics.vectormath.Vector3 position);
- ctor public Matrix4(androidx.compose.ui.graphics.vectormath.Matrix4 m);
- method public void assignColumns(androidx.compose.ui.graphics.vectormath.Matrix4 other);
- method public void assignFromStorage(java.util.List<java.lang.Float> storage);
- method public androidx.compose.ui.graphics.vectormath.Vector4 component1();
- method public androidx.compose.ui.graphics.vectormath.Vector4 component2();
- method public androidx.compose.ui.graphics.vectormath.Vector4 component3();
- method public androidx.compose.ui.graphics.vectormath.Vector4 component4();
- method public androidx.compose.ui.graphics.vectormath.Matrix4 copy(androidx.compose.ui.graphics.vectormath.Vector4 x, androidx.compose.ui.graphics.vectormath.Vector4 y, androidx.compose.ui.graphics.vectormath.Vector4 z, androidx.compose.ui.graphics.vectormath.Vector4 w);
- method public float copyInverse(androidx.compose.ui.graphics.vectormath.Matrix4 arg);
- method public operator androidx.compose.ui.graphics.vectormath.Matrix4 dec();
- method public operator androidx.compose.ui.graphics.vectormath.Matrix4 div(float v);
- method public operator androidx.compose.ui.graphics.vectormath.Vector4 get(int column);
- method public operator float get(int column, int row);
- method public operator androidx.compose.ui.graphics.vectormath.Vector4 get(androidx.compose.ui.graphics.vectormath.MatrixColumn column);
- method public operator float get(androidx.compose.ui.graphics.vectormath.MatrixColumn column, int row);
- method public float getDeterminant();
- method public inline androidx.compose.ui.graphics.vectormath.Vector3 getForward();
- method public inline java.util.List<java.lang.Float> getM4storage();
- method public inline androidx.compose.ui.graphics.vectormath.Vector3 getPosition();
- method public inline androidx.compose.ui.graphics.vectormath.Vector3 getRight();
- method public androidx.compose.ui.graphics.vectormath.Vector3 getRotation();
- method public androidx.compose.ui.graphics.vectormath.Vector4 getRow(int row);
- method public inline androidx.compose.ui.graphics.vectormath.Vector3 getScale();
- method public inline androidx.compose.ui.graphics.vectormath.Vector3 getTranslation();
- method public inline androidx.compose.ui.graphics.vectormath.Vector3 getUp();
- method public inline androidx.compose.ui.graphics.vectormath.Matrix3 getUpperLeft();
- method public androidx.compose.ui.graphics.vectormath.Vector4 getW();
- method public androidx.compose.ui.graphics.vectormath.Vector4 getX();
- method public androidx.compose.ui.graphics.vectormath.Vector4 getY();
- method public androidx.compose.ui.graphics.vectormath.Vector4 getZ();
- method public operator androidx.compose.ui.graphics.vectormath.Matrix4 inc();
- method public float invert();
- method public operator androidx.compose.ui.graphics.vectormath.Matrix4 minus(float v);
- method public androidx.compose.ui.graphics.vectormath.Vector3 perspectiveTransform(androidx.compose.ui.graphics.vectormath.Vector3 arg);
- method public operator androidx.compose.ui.graphics.vectormath.Matrix4 plus(float v);
- method public void rotateX(float radians);
- method public void rotateY(float radians);
- method public void rotateZ(float radians);
- method public void scale(Object x, Float? y = null, Float? z = null);
- method public operator void set(int column, androidx.compose.ui.graphics.vectormath.Vector4 v);
- method public operator void set(int column, int row, float v);
- method public inline void setForward(androidx.compose.ui.graphics.vectormath.Vector3 value);
- method public void setFrom(androidx.compose.ui.graphics.vectormath.Matrix4 arg);
- method public inline void setPosition(androidx.compose.ui.graphics.vectormath.Vector3 value);
- method public inline void setRight(androidx.compose.ui.graphics.vectormath.Vector3 value);
- method public void setTranslationRaw(float x, float y, float z);
- method public inline void setUp(androidx.compose.ui.graphics.vectormath.Vector3 value);
- method public void setW(androidx.compose.ui.graphics.vectormath.Vector4 p);
- method public void setX(androidx.compose.ui.graphics.vectormath.Vector4 p);
- method public void setY(androidx.compose.ui.graphics.vectormath.Vector4 p);
- method public void setZ(androidx.compose.ui.graphics.vectormath.Vector4 p);
- method public operator androidx.compose.ui.graphics.vectormath.Matrix4 times(float v);
- method public operator androidx.compose.ui.graphics.vectormath.Matrix4 times(androidx.compose.ui.graphics.vectormath.Matrix4 m);
- method public operator androidx.compose.ui.graphics.vectormath.Vector4 times(androidx.compose.ui.graphics.vectormath.Vector4 v);
- method public operator void timesAssign(androidx.compose.ui.graphics.vectormath.Matrix4 m);
- method public float[] toFloatArray();
- method public void translate(Object x, float y = 0.0f, float z = 0.0f);
- method public operator androidx.compose.ui.graphics.vectormath.Matrix4 unaryMinus();
- property public final float determinant;
- property public final inline androidx.compose.ui.graphics.vectormath.Vector3 forward;
- property public final inline java.util.List<java.lang.Float> m4storage;
- property public final inline androidx.compose.ui.graphics.vectormath.Vector3 position;
- property public final inline androidx.compose.ui.graphics.vectormath.Vector3 right;
- property public final androidx.compose.ui.graphics.vectormath.Vector3 rotation;
- property public final inline androidx.compose.ui.graphics.vectormath.Vector3 scale;
- property public final inline androidx.compose.ui.graphics.vectormath.Vector3 translation;
- property public final inline androidx.compose.ui.graphics.vectormath.Vector3 up;
- property public final inline androidx.compose.ui.graphics.vectormath.Matrix3 upperLeft;
- field public static final androidx.compose.ui.graphics.vectormath.Matrix4.Companion Companion;
- }
-
- public static final class Matrix4.Companion {
- method public androidx.compose.ui.graphics.vectormath.Matrix4 diagonal3(androidx.compose.ui.graphics.vectormath.Vector3 scale);
- method public androidx.compose.ui.graphics.vectormath.Matrix4 diagonal3Values(float x, float y, float z);
- method public androidx.compose.ui.graphics.vectormath.Matrix4 identity();
- method public androidx.compose.ui.graphics.vectormath.Matrix4 of(float... a);
- method public androidx.compose.ui.graphics.vectormath.Matrix4 rotationX(float radians);
- method public androidx.compose.ui.graphics.vectormath.Matrix4 rotationY(float radians);
- method public androidx.compose.ui.graphics.vectormath.Matrix4 rotationZ(float radians);
- method public androidx.compose.ui.graphics.vectormath.Matrix4 translation(androidx.compose.ui.graphics.vectormath.Vector3 translation);
- method public androidx.compose.ui.graphics.vectormath.Matrix4 translationValues(float x, float y, float z);
- method public androidx.compose.ui.graphics.vectormath.Matrix4 zero();
- }
-
- public final class Matrix4Kt {
- method public static Float? getAsScale(androidx.compose.ui.graphics.vectormath.Matrix4);
- method public static androidx.compose.ui.geometry.Offset? getAsTranslation(androidx.compose.ui.graphics.vectormath.Matrix4);
- method public static androidx.compose.ui.geometry.Rect inverseTransformRect(androidx.compose.ui.graphics.vectormath.Matrix4 transform, androidx.compose.ui.geometry.Rect rect);
- method public static boolean isIdentity(androidx.compose.ui.graphics.vectormath.Matrix4);
- method public static boolean matrixEquals(androidx.compose.ui.graphics.vectormath.Matrix4? a, androidx.compose.ui.graphics.vectormath.Matrix4? b);
- method public static long transformPoint-uF5TQos(androidx.compose.ui.graphics.vectormath.Matrix4, long point);
- method public static androidx.compose.ui.geometry.Rect transformRect(androidx.compose.ui.graphics.vectormath.Matrix4, androidx.compose.ui.geometry.Rect rect);
- }
-
- public enum MatrixColumn {
- method public static androidx.compose.ui.graphics.vectormath.MatrixColumn valueOf(String name) throws java.lang.IllegalArgumentException;
- method public static androidx.compose.ui.graphics.vectormath.MatrixColumn[] values();
- enum_constant public static final androidx.compose.ui.graphics.vectormath.MatrixColumn W;
- enum_constant public static final androidx.compose.ui.graphics.vectormath.MatrixColumn X;
- enum_constant public static final androidx.compose.ui.graphics.vectormath.MatrixColumn Y;
- enum_constant public static final androidx.compose.ui.graphics.vectormath.MatrixColumn Z;
- }
-
- public final class MatrixExtensionsKt {
- method public static androidx.compose.ui.graphics.vectormath.Matrix3 inverse(androidx.compose.ui.graphics.vectormath.Matrix3 m);
- method public static androidx.compose.ui.graphics.vectormath.Matrix4 inverse(androidx.compose.ui.graphics.vectormath.Matrix4 m);
- method public static androidx.compose.ui.graphics.vectormath.Matrix4 lookAt(androidx.compose.ui.graphics.vectormath.Vector3 eye, androidx.compose.ui.graphics.vectormath.Vector3 target, androidx.compose.ui.graphics.vectormath.Vector3 up = androidx.compose.ui.graphics.vectormath.Vector3(1.0));
- method public static androidx.compose.ui.graphics.vectormath.Matrix4 lookTowards(androidx.compose.ui.graphics.vectormath.Vector3 eye, androidx.compose.ui.graphics.vectormath.Vector3 forward, androidx.compose.ui.graphics.vectormath.Vector3 up = androidx.compose.ui.graphics.vectormath.Vector3(1.0));
- method public static androidx.compose.ui.graphics.vectormath.Matrix4 normal(androidx.compose.ui.graphics.vectormath.Matrix4 m);
- method public static androidx.compose.ui.graphics.vectormath.Matrix4 ortho(float l, float r, float b, float t, float n, float f);
- method public static androidx.compose.ui.graphics.vectormath.Matrix4 perspective(float fov, float ratio, float near, float far);
- method public static androidx.compose.ui.graphics.vectormath.Matrix4 rotation(androidx.compose.ui.graphics.vectormath.Matrix4 m);
- method public static androidx.compose.ui.graphics.vectormath.Matrix4 rotation(androidx.compose.ui.graphics.vectormath.Vector3 d);
- method public static androidx.compose.ui.graphics.vectormath.Matrix4 rotation(androidx.compose.ui.graphics.vectormath.Vector3 axis, float angle);
- method public static androidx.compose.ui.graphics.vectormath.Matrix4 scale(androidx.compose.ui.graphics.vectormath.Vector3 s);
- method public static androidx.compose.ui.graphics.vectormath.Matrix4 scale(androidx.compose.ui.graphics.vectormath.Matrix4 m);
- method public static androidx.compose.ui.graphics.vectormath.Matrix4 translation(androidx.compose.ui.graphics.vectormath.Vector3 t);
- method public static androidx.compose.ui.graphics.vectormath.Matrix4 translation(androidx.compose.ui.graphics.vectormath.Matrix4 m);
- method public static androidx.compose.ui.graphics.vectormath.Matrix3 transpose(androidx.compose.ui.graphics.vectormath.Matrix3 m);
- method public static androidx.compose.ui.graphics.vectormath.Matrix4 transpose(androidx.compose.ui.graphics.vectormath.Matrix4 m);
- }
-
- public final class ScalarKt {
- method public static inline float degrees(float v);
- method public static inline float radians(float v);
- field public static final float FOUR_PI = 12.566371f;
- field public static final float HALF_PI = 1.5707964f;
- field public static final float INV_FOUR_PI = 0.07957747f;
- field public static final float INV_PI = 0.31830987f;
- field public static final float INV_TWO_PI = 0.15915494f;
- field public static final float PI = 3.1415927f;
- field public static final float TWO_PI = 6.2831855f;
- }
-
- public final class Vector2 {
- ctor public Vector2(float x, float y);
- ctor public Vector2();
- ctor public Vector2(androidx.compose.ui.graphics.vectormath.Vector2 v);
- method public float component1();
- method public float component2();
- method public androidx.compose.ui.graphics.vectormath.Vector2 copy(float x, float y);
- method public operator androidx.compose.ui.graphics.vectormath.Vector2 dec();
- method public inline operator androidx.compose.ui.graphics.vectormath.Vector2 div(float v);
- method public inline operator androidx.compose.ui.graphics.vectormath.Vector2 div(androidx.compose.ui.graphics.vectormath.Vector2 v);
- method public operator float get(androidx.compose.ui.graphics.vectormath.VectorComponent index);
- method public operator androidx.compose.ui.graphics.vectormath.Vector2 get(androidx.compose.ui.graphics.vectormath.VectorComponent index1, androidx.compose.ui.graphics.vectormath.VectorComponent index2);
- method public operator float get(int index);
- method public operator androidx.compose.ui.graphics.vectormath.Vector2 get(int index1, int index2);
- method public inline float getG();
- method public inline float getR();
- method public inline androidx.compose.ui.graphics.vectormath.Vector2 getRg();
- method public inline float getS();
- method public inline androidx.compose.ui.graphics.vectormath.Vector2 getSt();
- method public inline float getT();
- method public inline java.util.List<java.lang.Float> getV2storage();
- method public float getX();
- method public inline androidx.compose.ui.graphics.vectormath.Vector2 getXy();
- method public float getY();
- method public operator androidx.compose.ui.graphics.vectormath.Vector2 inc();
- method public inline operator androidx.compose.ui.graphics.vectormath.Vector2 minus(float v);
- method public inline operator androidx.compose.ui.graphics.vectormath.Vector2 minus(androidx.compose.ui.graphics.vectormath.Vector2 v);
- method public inline operator androidx.compose.ui.graphics.vectormath.Vector2 plus(float v);
- method public inline operator androidx.compose.ui.graphics.vectormath.Vector2 plus(androidx.compose.ui.graphics.vectormath.Vector2 v);
- method public operator void set(int index, float v);
- method public operator void set(int index1, int index2, float v);
- method public operator void set(androidx.compose.ui.graphics.vectormath.VectorComponent index, float v);
- method public operator void set(androidx.compose.ui.graphics.vectormath.VectorComponent index1, androidx.compose.ui.graphics.vectormath.VectorComponent index2, float v);
- method public inline void setG(float value);
- method public inline void setR(float value);
- method public inline void setRg(androidx.compose.ui.graphics.vectormath.Vector2 value);
- method public inline void setS(float value);
- method public inline void setSt(androidx.compose.ui.graphics.vectormath.Vector2 value);
- method public inline void setT(float value);
- method public void setX(float p);
- method public inline void setXy(androidx.compose.ui.graphics.vectormath.Vector2 value);
- method public void setY(float p);
- method public inline operator androidx.compose.ui.graphics.vectormath.Vector2 times(float v);
- method public inline operator androidx.compose.ui.graphics.vectormath.Vector2 times(androidx.compose.ui.graphics.vectormath.Vector2 v);
- method public inline androidx.compose.ui.graphics.vectormath.Vector2 transform(kotlin.jvm.functions.Function1<? super java.lang.Float,java.lang.Float> block);
- method public operator androidx.compose.ui.graphics.vectormath.Vector2 unaryMinus();
- property public final inline float g;
- property public final inline float r;
- property public final inline androidx.compose.ui.graphics.vectormath.Vector2 rg;
- property public final inline float s;
- property public final inline androidx.compose.ui.graphics.vectormath.Vector2 st;
- property public final inline float t;
- property public final inline java.util.List<java.lang.Float> v2storage;
- property public final inline androidx.compose.ui.graphics.vectormath.Vector2 xy;
- }
-
- public final class Vector3 {
- ctor public Vector3(float x, float y, float z);
- ctor public Vector3();
- ctor public Vector3(androidx.compose.ui.graphics.vectormath.Vector2 v, float z);
- ctor public Vector3(androidx.compose.ui.graphics.vectormath.Vector3 v);
- method public float component1();
- method public float component2();
- method public float component3();
- method public androidx.compose.ui.graphics.vectormath.Vector3 copy(float x, float y, float z);
- method public operator androidx.compose.ui.graphics.vectormath.Vector3 dec();
- method public inline operator androidx.compose.ui.graphics.vectormath.Vector3 div(float v);
- method public inline operator androidx.compose.ui.graphics.vectormath.Vector3 div(androidx.compose.ui.graphics.vectormath.Vector2 v);
- method public inline operator androidx.compose.ui.graphics.vectormath.Vector3 div(androidx.compose.ui.graphics.vectormath.Vector3 v);
- method public operator float get(androidx.compose.ui.graphics.vectormath.VectorComponent index);
- method public operator androidx.compose.ui.graphics.vectormath.Vector2 get(androidx.compose.ui.graphics.vectormath.VectorComponent index1, androidx.compose.ui.graphics.vectormath.VectorComponent index2);
- method public operator androidx.compose.ui.graphics.vectormath.Vector3 get(androidx.compose.ui.graphics.vectormath.VectorComponent index1, androidx.compose.ui.graphics.vectormath.VectorComponent index2, androidx.compose.ui.graphics.vectormath.VectorComponent index3);
- method public operator float get(int index);
- method public operator androidx.compose.ui.graphics.vectormath.Vector2 get(int index1, int index2);
- method public operator androidx.compose.ui.graphics.vectormath.Vector3 get(int index1, int index2, int index3);
- method public inline float getB();
- method public inline float getG();
- method public inline float getP();
- method public inline float getR();
- method public inline androidx.compose.ui.graphics.vectormath.Vector2 getRg();
- method public inline androidx.compose.ui.graphics.vectormath.Vector3 getRgb();
- method public inline float getS();
- method public inline androidx.compose.ui.graphics.vectormath.Vector2 getSt();
- method public inline androidx.compose.ui.graphics.vectormath.Vector3 getStp();
- method public inline float getT();
- method public inline java.util.List<java.lang.Float> getV3storage();
- method public float getX();
- method public inline androidx.compose.ui.graphics.vectormath.Vector2 getXy();
- method public inline androidx.compose.ui.graphics.vectormath.Vector3 getXyz();
- method public float getY();
- method public float getZ();
- method public operator androidx.compose.ui.graphics.vectormath.Vector3 inc();
- method public inline operator androidx.compose.ui.graphics.vectormath.Vector3 minus(float v);
- method public inline operator androidx.compose.ui.graphics.vectormath.Vector3 minus(androidx.compose.ui.graphics.vectormath.Vector2 v);
- method public inline operator androidx.compose.ui.graphics.vectormath.Vector3 minus(androidx.compose.ui.graphics.vectormath.Vector3 v);
- method public inline operator androidx.compose.ui.graphics.vectormath.Vector3 plus(float v);
- method public inline operator androidx.compose.ui.graphics.vectormath.Vector3 plus(androidx.compose.ui.graphics.vectormath.Vector2 v);
- method public inline operator androidx.compose.ui.graphics.vectormath.Vector3 plus(androidx.compose.ui.graphics.vectormath.Vector3 v);
- method public operator void set(int index, float v);
- method public operator void set(int index1, int index2, float v);
- method public operator void set(int index1, int index2, int index3, float v);
- method public operator void set(androidx.compose.ui.graphics.vectormath.VectorComponent index, float v);
- method public operator void set(androidx.compose.ui.graphics.vectormath.VectorComponent index1, androidx.compose.ui.graphics.vectormath.VectorComponent index2, float v);
- method public operator void set(androidx.compose.ui.graphics.vectormath.VectorComponent index1, androidx.compose.ui.graphics.vectormath.VectorComponent index2, androidx.compose.ui.graphics.vectormath.VectorComponent index3, float v);
- method public inline void setB(float value);
- method public inline void setG(float value);
- method public inline void setP(float value);
- method public inline void setR(float value);
- method public inline void setRg(androidx.compose.ui.graphics.vectormath.Vector2 value);
- method public inline void setRgb(androidx.compose.ui.graphics.vectormath.Vector3 value);
- method public inline void setS(float value);
- method public inline void setSt(androidx.compose.ui.graphics.vectormath.Vector2 value);
- method public inline void setStp(androidx.compose.ui.graphics.vectormath.Vector3 value);
- method public inline void setT(float value);
- method public void setX(float p);
- method public inline void setXy(androidx.compose.ui.graphics.vectormath.Vector2 value);
- method public inline void setXyz(androidx.compose.ui.graphics.vectormath.Vector3 value);
- method public void setY(float p);
- method public void setZ(float p);
- method public inline operator androidx.compose.ui.graphics.vectormath.Vector3 times(float v);
- method public inline operator androidx.compose.ui.graphics.vectormath.Vector3 times(androidx.compose.ui.graphics.vectormath.Vector2 v);
- method public inline operator androidx.compose.ui.graphics.vectormath.Vector3 times(androidx.compose.ui.graphics.vectormath.Vector3 v);
- method public inline androidx.compose.ui.graphics.vectormath.Vector3 transform(kotlin.jvm.functions.Function1<? super java.lang.Float,java.lang.Float> block);
- method public operator androidx.compose.ui.graphics.vectormath.Vector3 unaryMinus();
- property public final inline float b;
- property public final inline float g;
- property public final inline float p;
- property public final inline float r;
- property public final inline androidx.compose.ui.graphics.vectormath.Vector2 rg;
- property public final inline androidx.compose.ui.graphics.vectormath.Vector3 rgb;
- property public final inline float s;
- property public final inline androidx.compose.ui.graphics.vectormath.Vector2 st;
- property public final inline androidx.compose.ui.graphics.vectormath.Vector3 stp;
- property public final inline float t;
- property public final inline java.util.List<java.lang.Float> v3storage;
- property public final inline androidx.compose.ui.graphics.vectormath.Vector2 xy;
- property public final inline androidx.compose.ui.graphics.vectormath.Vector3 xyz;
- }
-
- public final class Vector4 {
- ctor public Vector4(float x, float y, float z, float w);
- ctor public Vector4();
- ctor public Vector4(androidx.compose.ui.graphics.vectormath.Vector2 v, float z, float w);
- ctor public Vector4(androidx.compose.ui.graphics.vectormath.Vector3 v, float w);
- ctor public Vector4(androidx.compose.ui.graphics.vectormath.Vector4 v);
- method public void assignFromStorage(java.util.List<java.lang.Float> storage);
- method public float component1();
- method public float component2();
- method public float component3();
- method public float component4();
- method public androidx.compose.ui.graphics.vectormath.Vector4 copy(float x, float y, float z, float w);
- method public operator androidx.compose.ui.graphics.vectormath.Vector4 dec();
- method public inline operator androidx.compose.ui.graphics.vectormath.Vector4 div(float v);
- method public inline operator androidx.compose.ui.graphics.vectormath.Vector4 div(androidx.compose.ui.graphics.vectormath.Vector2 v);
- method public inline operator androidx.compose.ui.graphics.vectormath.Vector4 div(androidx.compose.ui.graphics.vectormath.Vector3 v);
- method public inline operator androidx.compose.ui.graphics.vectormath.Vector4 div(androidx.compose.ui.graphics.vectormath.Vector4 v);
- method public operator float get(androidx.compose.ui.graphics.vectormath.VectorComponent index);
- method public operator androidx.compose.ui.graphics.vectormath.Vector2 get(androidx.compose.ui.graphics.vectormath.VectorComponent index1, androidx.compose.ui.graphics.vectormath.VectorComponent index2);
- method public operator androidx.compose.ui.graphics.vectormath.Vector3 get(androidx.compose.ui.graphics.vectormath.VectorComponent index1, androidx.compose.ui.graphics.vectormath.VectorComponent index2, androidx.compose.ui.graphics.vectormath.VectorComponent index3);
- method public operator androidx.compose.ui.graphics.vectormath.Vector4 get(androidx.compose.ui.graphics.vectormath.VectorComponent index1, androidx.compose.ui.graphics.vectormath.VectorComponent index2, androidx.compose.ui.graphics.vectormath.VectorComponent index3, androidx.compose.ui.graphics.vectormath.VectorComponent index4);
- method public operator float get(int index);
- method public operator androidx.compose.ui.graphics.vectormath.Vector2 get(int index1, int index2);
- method public operator androidx.compose.ui.graphics.vectormath.Vector3 get(int index1, int index2, int index3);
- method public operator androidx.compose.ui.graphics.vectormath.Vector4 get(int index1, int index2, int index3, int index4);
- method public inline float getA();
- method public inline float getB();
- method public inline float getG();
- method public inline float getP();
- method public inline float getQ();
- method public inline float getR();
- method public inline androidx.compose.ui.graphics.vectormath.Vector2 getRg();
- method public inline androidx.compose.ui.graphics.vectormath.Vector3 getRgb();
- method public inline androidx.compose.ui.graphics.vectormath.Vector4 getRgba();
- method public inline float getS();
- method public inline androidx.compose.ui.graphics.vectormath.Vector2 getSt();
- method public inline androidx.compose.ui.graphics.vectormath.Vector3 getStp();
- method public inline androidx.compose.ui.graphics.vectormath.Vector4 getStpq();
- method public inline float getT();
- method public inline java.util.List<java.lang.Float> getV4storage();
- method public float getW();
- method public float getX();
- method public inline androidx.compose.ui.graphics.vectormath.Vector2 getXy();
- method public inline androidx.compose.ui.graphics.vectormath.Vector3 getXyz();
- method public inline androidx.compose.ui.graphics.vectormath.Vector4 getXyzw();
- method public float getY();
- method public float getZ();
- method public operator androidx.compose.ui.graphics.vectormath.Vector4 inc();
- method public inline operator androidx.compose.ui.graphics.vectormath.Vector4 minus(float v);
- method public inline operator androidx.compose.ui.graphics.vectormath.Vector4 minus(androidx.compose.ui.graphics.vectormath.Vector2 v);
- method public inline operator androidx.compose.ui.graphics.vectormath.Vector4 minus(androidx.compose.ui.graphics.vectormath.Vector3 v);
- method public inline operator androidx.compose.ui.graphics.vectormath.Vector4 minus(androidx.compose.ui.graphics.vectormath.Vector4 v);
- method public inline operator androidx.compose.ui.graphics.vectormath.Vector4 plus(float v);
- method public inline operator androidx.compose.ui.graphics.vectormath.Vector4 plus(androidx.compose.ui.graphics.vectormath.Vector2 v);
- method public inline operator androidx.compose.ui.graphics.vectormath.Vector4 plus(androidx.compose.ui.graphics.vectormath.Vector3 v);
- method public inline operator androidx.compose.ui.graphics.vectormath.Vector4 plus(androidx.compose.ui.graphics.vectormath.Vector4 v);
- method public operator void set(int index, float v);
- method public operator void set(int index1, int index2, float v);
- method public operator void set(int index1, int index2, int index3, float v);
- method public operator void set(int index1, int index2, int index3, int index4, float v);
- method public operator void set(androidx.compose.ui.graphics.vectormath.VectorComponent index, float v);
- method public operator void set(androidx.compose.ui.graphics.vectormath.VectorComponent index1, androidx.compose.ui.graphics.vectormath.VectorComponent index2, float v);
- method public operator void set(androidx.compose.ui.graphics.vectormath.VectorComponent index1, androidx.compose.ui.graphics.vectormath.VectorComponent index2, androidx.compose.ui.graphics.vectormath.VectorComponent index3, float v);
- method public operator void set(androidx.compose.ui.graphics.vectormath.VectorComponent index1, androidx.compose.ui.graphics.vectormath.VectorComponent index2, androidx.compose.ui.graphics.vectormath.VectorComponent index3, androidx.compose.ui.graphics.vectormath.VectorComponent index4, float v);
- method public inline void setA(float value);
- method public inline void setB(float value);
- method public inline void setG(float value);
- method public inline void setP(float value);
- method public inline void setQ(float value);
- method public inline void setR(float value);
- method public inline void setRg(androidx.compose.ui.graphics.vectormath.Vector2 value);
- method public inline void setRgb(androidx.compose.ui.graphics.vectormath.Vector3 value);
- method public inline void setRgba(androidx.compose.ui.graphics.vectormath.Vector4 value);
- method public inline void setS(float value);
- method public inline void setSt(androidx.compose.ui.graphics.vectormath.Vector2 value);
- method public inline void setStp(androidx.compose.ui.graphics.vectormath.Vector3 value);
- method public inline void setStpq(androidx.compose.ui.graphics.vectormath.Vector4 value);
- method public inline void setT(float value);
- method public void setW(float p);
- method public void setX(float p);
- method public inline void setXy(androidx.compose.ui.graphics.vectormath.Vector2 value);
- method public inline void setXyz(androidx.compose.ui.graphics.vectormath.Vector3 value);
- method public inline void setXyzw(androidx.compose.ui.graphics.vectormath.Vector4 value);
- method public void setY(float p);
- method public void setZ(float p);
- method public inline operator androidx.compose.ui.graphics.vectormath.Vector4 times(float v);
- method public inline operator androidx.compose.ui.graphics.vectormath.Vector4 times(androidx.compose.ui.graphics.vectormath.Vector2 v);
- method public inline operator androidx.compose.ui.graphics.vectormath.Vector4 times(androidx.compose.ui.graphics.vectormath.Vector3 v);
- method public inline operator androidx.compose.ui.graphics.vectormath.Vector4 times(androidx.compose.ui.graphics.vectormath.Vector4 v);
- method public inline androidx.compose.ui.graphics.vectormath.Vector4 transform(kotlin.jvm.functions.Function1<? super java.lang.Float,java.lang.Float> block);
- method public operator androidx.compose.ui.graphics.vectormath.Vector4 unaryMinus();
- property public final inline float a;
- property public final inline float b;
- property public final inline float g;
- property public final inline float p;
- property public final inline float q;
- property public final inline float r;
- property public final inline androidx.compose.ui.graphics.vectormath.Vector2 rg;
- property public final inline androidx.compose.ui.graphics.vectormath.Vector3 rgb;
- property public final inline androidx.compose.ui.graphics.vectormath.Vector4 rgba;
- property public final inline float s;
- property public final inline androidx.compose.ui.graphics.vectormath.Vector2 st;
- property public final inline androidx.compose.ui.graphics.vectormath.Vector3 stp;
- property public final inline androidx.compose.ui.graphics.vectormath.Vector4 stpq;
- property public final inline float t;
- property public final inline java.util.List<java.lang.Float> v4storage;
- property public final inline androidx.compose.ui.graphics.vectormath.Vector2 xy;
- property public final inline androidx.compose.ui.graphics.vectormath.Vector3 xyz;
- property public final inline androidx.compose.ui.graphics.vectormath.Vector4 xyzw;
- }
-
- public enum VectorComponent {
- method public static androidx.compose.ui.graphics.vectormath.VectorComponent valueOf(String name) throws java.lang.IllegalArgumentException;
- method public static androidx.compose.ui.graphics.vectormath.VectorComponent[] values();
- enum_constant public static final androidx.compose.ui.graphics.vectormath.VectorComponent A;
- enum_constant public static final androidx.compose.ui.graphics.vectormath.VectorComponent B;
- enum_constant public static final androidx.compose.ui.graphics.vectormath.VectorComponent G;
- enum_constant public static final androidx.compose.ui.graphics.vectormath.VectorComponent P;
- enum_constant public static final androidx.compose.ui.graphics.vectormath.VectorComponent Q;
- enum_constant public static final androidx.compose.ui.graphics.vectormath.VectorComponent R;
- enum_constant public static final androidx.compose.ui.graphics.vectormath.VectorComponent S;
- enum_constant public static final androidx.compose.ui.graphics.vectormath.VectorComponent T;
- enum_constant public static final androidx.compose.ui.graphics.vectormath.VectorComponent W;
- enum_constant public static final androidx.compose.ui.graphics.vectormath.VectorComponent X;
- enum_constant public static final androidx.compose.ui.graphics.vectormath.VectorComponent Y;
- enum_constant public static final androidx.compose.ui.graphics.vectormath.VectorComponent Z;
- }
-
- public final class VectorExtensionsKt {
- method public static inline androidx.compose.ui.graphics.vectormath.Vector2 abs(androidx.compose.ui.graphics.vectormath.Vector2 v);
- method public static inline androidx.compose.ui.graphics.vectormath.Vector3 abs(androidx.compose.ui.graphics.vectormath.Vector3 v);
- method public static inline androidx.compose.ui.graphics.vectormath.Vector4 abs(androidx.compose.ui.graphics.vectormath.Vector4 v);
- method public static inline androidx.compose.ui.graphics.vectormath.Vector2 coerceIn(androidx.compose.ui.graphics.vectormath.Vector2, float min, float max);
- method public static inline androidx.compose.ui.graphics.vectormath.Vector2 coerceIn(androidx.compose.ui.graphics.vectormath.Vector2, androidx.compose.ui.graphics.vectormath.Vector2 min, androidx.compose.ui.graphics.vectormath.Vector2 max);
- method public static inline androidx.compose.ui.graphics.vectormath.Vector3 coerceIn(androidx.compose.ui.graphics.vectormath.Vector3, float min, float max);
- method public static inline androidx.compose.ui.graphics.vectormath.Vector3 coerceIn(androidx.compose.ui.graphics.vectormath.Vector3, androidx.compose.ui.graphics.vectormath.Vector3 min, androidx.compose.ui.graphics.vectormath.Vector3 max);
- method public static inline androidx.compose.ui.graphics.vectormath.Vector4 coerceIn(androidx.compose.ui.graphics.vectormath.Vector4, float min, float max);
- method public static inline androidx.compose.ui.graphics.vectormath.Vector4 coerceIn(androidx.compose.ui.graphics.vectormath.Vector4, androidx.compose.ui.graphics.vectormath.Vector4 min, androidx.compose.ui.graphics.vectormath.Vector4 max);
- method public static inline androidx.compose.ui.graphics.vectormath.Vector3 cross(androidx.compose.ui.graphics.vectormath.Vector3 a, androidx.compose.ui.graphics.vectormath.Vector3 b);
- method public static inline float distance(androidx.compose.ui.graphics.vectormath.Vector2 a, androidx.compose.ui.graphics.vectormath.Vector2 b);
- method public static inline float distance(androidx.compose.ui.graphics.vectormath.Vector3 a, androidx.compose.ui.graphics.vectormath.Vector3 b);
- method public static inline float distance(androidx.compose.ui.graphics.vectormath.Vector4 a, androidx.compose.ui.graphics.vectormath.Vector4 b);
- method public static inline operator androidx.compose.ui.graphics.vectormath.Vector2 div(float, androidx.compose.ui.graphics.vectormath.Vector2 v);
- method public static inline operator androidx.compose.ui.graphics.vectormath.Vector3 div(float, androidx.compose.ui.graphics.vectormath.Vector3 v);
- method public static inline operator androidx.compose.ui.graphics.vectormath.Vector4 div(float, androidx.compose.ui.graphics.vectormath.Vector4 v);
- method public static inline float dot(androidx.compose.ui.graphics.vectormath.Vector2 a, androidx.compose.ui.graphics.vectormath.Vector2 b);
- method public static inline float dot(androidx.compose.ui.graphics.vectormath.Vector3 a, androidx.compose.ui.graphics.vectormath.Vector3 b);
- method public static inline float dot(androidx.compose.ui.graphics.vectormath.Vector4 a, androidx.compose.ui.graphics.vectormath.Vector4 b);
- method public static inline float length(androidx.compose.ui.graphics.vectormath.Vector2 v);
- method public static inline float length(androidx.compose.ui.graphics.vectormath.Vector3 v);
- method public static inline float length(androidx.compose.ui.graphics.vectormath.Vector4 v);
- method public static inline float length2(androidx.compose.ui.graphics.vectormath.Vector2 v);
- method public static inline float length2(androidx.compose.ui.graphics.vectormath.Vector3 v);
- method public static inline float length2(androidx.compose.ui.graphics.vectormath.Vector4 v);
- method public static inline float max(androidx.compose.ui.graphics.vectormath.Vector2 v);
- method public static inline androidx.compose.ui.graphics.vectormath.Vector2 max(androidx.compose.ui.graphics.vectormath.Vector2 a, androidx.compose.ui.graphics.vectormath.Vector2 b);
- method public static inline float max(androidx.compose.ui.graphics.vectormath.Vector3 v);
- method public static inline androidx.compose.ui.graphics.vectormath.Vector3 max(androidx.compose.ui.graphics.vectormath.Vector3 a, androidx.compose.ui.graphics.vectormath.Vector3 b);
- method public static inline float max(androidx.compose.ui.graphics.vectormath.Vector4 v);
- method public static inline androidx.compose.ui.graphics.vectormath.Vector4 max(androidx.compose.ui.graphics.vectormath.Vector4 a, androidx.compose.ui.graphics.vectormath.Vector4 b);
- method public static inline float min(androidx.compose.ui.graphics.vectormath.Vector2 v);
- method public static inline androidx.compose.ui.graphics.vectormath.Vector2 min(androidx.compose.ui.graphics.vectormath.Vector2 a, androidx.compose.ui.graphics.vectormath.Vector2 b);
- method public static inline float min(androidx.compose.ui.graphics.vectormath.Vector3 v);
- method public static inline androidx.compose.ui.graphics.vectormath.Vector3 min(androidx.compose.ui.graphics.vectormath.Vector3 a, androidx.compose.ui.graphics.vectormath.Vector3 b);
- method public static inline float min(androidx.compose.ui.graphics.vectormath.Vector4 v);
- method public static inline androidx.compose.ui.graphics.vectormath.Vector4 min(androidx.compose.ui.graphics.vectormath.Vector4 a, androidx.compose.ui.graphics.vectormath.Vector4 b);
- method public static inline operator androidx.compose.ui.graphics.vectormath.Vector2 minus(float, androidx.compose.ui.graphics.vectormath.Vector2 v);
- method public static inline operator androidx.compose.ui.graphics.vectormath.Vector3 minus(float, androidx.compose.ui.graphics.vectormath.Vector3 v);
- method public static inline operator androidx.compose.ui.graphics.vectormath.Vector4 minus(float, androidx.compose.ui.graphics.vectormath.Vector4 v);
- method public static androidx.compose.ui.graphics.vectormath.Vector2 normalize(androidx.compose.ui.graphics.vectormath.Vector2 v);
- method public static androidx.compose.ui.graphics.vectormath.Vector3 normalize(androidx.compose.ui.graphics.vectormath.Vector3 v);
- method public static androidx.compose.ui.graphics.vectormath.Vector4 normalize(androidx.compose.ui.graphics.vectormath.Vector4 v);
- method public static inline operator androidx.compose.ui.graphics.vectormath.Vector2 plus(float, androidx.compose.ui.graphics.vectormath.Vector2 v);
- method public static inline operator androidx.compose.ui.graphics.vectormath.Vector3 plus(float, androidx.compose.ui.graphics.vectormath.Vector3 v);
- method public static inline operator androidx.compose.ui.graphics.vectormath.Vector4 plus(float, androidx.compose.ui.graphics.vectormath.Vector4 v);
- method public static inline androidx.compose.ui.graphics.vectormath.Vector2 reflect(androidx.compose.ui.graphics.vectormath.Vector2 i, androidx.compose.ui.graphics.vectormath.Vector2 n);
- method public static inline androidx.compose.ui.graphics.vectormath.Vector3 reflect(androidx.compose.ui.graphics.vectormath.Vector3 i, androidx.compose.ui.graphics.vectormath.Vector3 n);
- method public static androidx.compose.ui.graphics.vectormath.Vector2 refract(androidx.compose.ui.graphics.vectormath.Vector2 i, androidx.compose.ui.graphics.vectormath.Vector2 n, float eta);
- method public static androidx.compose.ui.graphics.vectormath.Vector3 refract(androidx.compose.ui.graphics.vectormath.Vector3 i, androidx.compose.ui.graphics.vectormath.Vector3 n, float eta);
- method public static inline operator androidx.compose.ui.graphics.vectormath.Vector2 times(float, androidx.compose.ui.graphics.vectormath.Vector2 v);
- method public static inline operator androidx.compose.ui.graphics.vectormath.Vector3 times(float, androidx.compose.ui.graphics.vectormath.Vector3 v);
- method public static inline operator androidx.compose.ui.graphics.vectormath.Vector4 times(float, androidx.compose.ui.graphics.vectormath.Vector4 v);
- method public static inline androidx.compose.ui.graphics.vectormath.Vector2 transform(androidx.compose.ui.graphics.vectormath.Vector2 v, kotlin.jvm.functions.Function1<? super java.lang.Float,java.lang.Float> block);
- method public static inline androidx.compose.ui.graphics.vectormath.Vector3 transform(androidx.compose.ui.graphics.vectormath.Vector3 v, kotlin.jvm.functions.Function1<? super java.lang.Float,java.lang.Float> block);
- method public static inline androidx.compose.ui.graphics.vectormath.Vector4 transform(androidx.compose.ui.graphics.vectormath.Vector4 v, kotlin.jvm.functions.Function1<? super java.lang.Float,java.lang.Float> block);
- method public static inline infix androidx.compose.ui.graphics.vectormath.Vector3 x(androidx.compose.ui.graphics.vectormath.Vector3, androidx.compose.ui.graphics.vectormath.Vector3 v);
- }
-
-}
-
diff --git a/compose/ui/ui-graphics/api/public_plus_experimental_current.txt b/compose/ui/ui-graphics/api/public_plus_experimental_current.txt
index 2e6e854..9931c09 100644
--- a/compose/ui/ui-graphics/api/public_plus_experimental_current.txt
+++ b/compose/ui/ui-graphics/api/public_plus_experimental_current.txt
@@ -16,6 +16,11 @@
method public static androidx.compose.ui.graphics.ImageAsset imageFromResource(android.content.res.Resources res, int resId);
}
+ public final class AndroidMatrixConversionsKt {
+ method public static void setFrom-7lL006A(float[], android.graphics.Matrix matrix);
+ method public static void setFrom-8AuSnpc(android.graphics.Matrix, float[] matrix);
+ }
+
public final class AndroidPaint implements androidx.compose.ui.graphics.Paint {
ctor public AndroidPaint();
method public android.graphics.Paint asFrameworkPaint();
@@ -178,7 +183,7 @@
method public void clipPath(androidx.compose.ui.graphics.Path path, androidx.compose.ui.graphics.ClipOp clipOp = androidx.compose.ui.graphics.ClipOp.Intersect);
method public default void clipRect(androidx.compose.ui.geometry.Rect rect, androidx.compose.ui.graphics.ClipOp clipOp = androidx.compose.ui.graphics.ClipOp.Intersect);
method public void clipRect(float left, float top, float right, float bottom, androidx.compose.ui.graphics.ClipOp clipOp = androidx.compose.ui.graphics.ClipOp.Intersect);
- method public void concat(androidx.compose.ui.graphics.vectormath.Matrix4 matrix4);
+ method public void concat-58bKbWc(float[] matrix);
method public void disableZ();
method public default void drawArc(androidx.compose.ui.geometry.Rect rect, float startAngle, float sweepAngle, boolean useCenter, androidx.compose.ui.graphics.Paint paint);
method public void drawArc(float left, float top, float right, float bottom, float startAngle, float sweepAngle, boolean useCenter, androidx.compose.ui.graphics.Paint paint);
@@ -307,6 +312,9 @@
method public static inline long useOrElse-iYUlWp8(long, kotlin.jvm.functions.Function0<androidx.compose.ui.graphics.Color> block);
}
+ public final class DegreesKt {
+ }
+
public enum FilterQuality {
method public static androidx.compose.ui.graphics.FilterQuality valueOf(String name) throws java.lang.IllegalArgumentException;
method public static androidx.compose.ui.graphics.FilterQuality[] values();
@@ -353,6 +361,34 @@
method @androidx.compose.runtime.Immutable public androidx.compose.ui.graphics.LinearGradient copy(java.util.List<androidx.compose.ui.graphics.Color> colors, java.util.List<java.lang.Float>? stops, float startX, float startY, float endX, float endY, androidx.compose.ui.graphics.TileMode tileMode);
}
+ public final inline class Matrix {
+ ctor public Matrix();
+ method public static float[]! constructor-impl(float[] values);
+ method public static inline boolean equals-impl(float[]! p, Object? p1);
+ method public static boolean equals-impl0(float[] p1, float[] p2);
+ method public static inline operator float get-impl(float[] $this, int row, int column);
+ method public float[] getValues();
+ method public static inline int hashCode-impl(float[]! p);
+ method public static void invert-impl(float[]! $this);
+ method public static androidx.compose.ui.geometry.Rect map-impl(float[] $this, androidx.compose.ui.geometry.Rect rect);
+ method public static void map-impl(float[] $this, androidx.compose.ui.geometry.MutableRect rect);
+ method public static long map-k-4lQ0M(float[] $this, long point);
+ method public static void reset-impl(float[]! $this);
+ method public static void rotateX-impl(float[] $this, float degrees);
+ method public static void rotateY-impl(float[] $this, float degrees);
+ method public static void rotateZ-impl(float[] $this, float degrees);
+ method public static void scale-impl(float[] $this, float x = 1f, float y = 1f, float z = 1f);
+ method public static inline operator void set-impl(float[] $this, int row, int column, float v);
+ method public static void setFrom-58bKbWc(float[] $this, float[] matrix);
+ method public static operator void timesAssign-58bKbWc(float[] $this, float[] m);
+ method public static String toString-impl(float[]! $this);
+ method public static void translate-impl(float[] $this, float x = 0f, float y = 0f, float z = 0f);
+ }
+
+ public final class MatrixKt {
+ method public static boolean isIdentity-58bKbWc(float[]);
+ }
+
public abstract sealed class Outline {
}
@@ -868,7 +904,7 @@
method public void inset(float left, float top, float right, float bottom);
method public void rotate(float degrees, float pivotX = center.x, float pivotY = center.y);
method public void scale(float scaleX, float scaleY = scaleX, float pivotX = center.x, float pivotY = center.y);
- method public void transform(androidx.compose.ui.graphics.vectormath.Matrix4 matrix);
+ method public void transform-58bKbWc(float[] matrix);
method public void translate(float left = 0.0f, float top = 0.0f);
property public default long center;
property public abstract long size;
@@ -1197,512 +1233,3 @@
}
-package androidx.compose.ui.graphics.vectormath {
-
- public final class Matrix3 {
- ctor public Matrix3(androidx.compose.ui.graphics.vectormath.Vector3 x, androidx.compose.ui.graphics.vectormath.Vector3 y, androidx.compose.ui.graphics.vectormath.Vector3 z);
- ctor public Matrix3();
- ctor public Matrix3(androidx.compose.ui.graphics.vectormath.Matrix3 m);
- method public androidx.compose.ui.graphics.vectormath.Vector3 component1();
- method public androidx.compose.ui.graphics.vectormath.Vector3 component2();
- method public androidx.compose.ui.graphics.vectormath.Vector3 component3();
- method public androidx.compose.ui.graphics.vectormath.Matrix3 copy(androidx.compose.ui.graphics.vectormath.Vector3 x, androidx.compose.ui.graphics.vectormath.Vector3 y, androidx.compose.ui.graphics.vectormath.Vector3 z);
- method public operator androidx.compose.ui.graphics.vectormath.Matrix3 dec();
- method public operator androidx.compose.ui.graphics.vectormath.Matrix3 div(float v);
- method public operator androidx.compose.ui.graphics.vectormath.Vector3 get(int column);
- method public operator float get(int column, int row);
- method public operator androidx.compose.ui.graphics.vectormath.Vector3 get(androidx.compose.ui.graphics.vectormath.MatrixColumn column);
- method public operator float get(androidx.compose.ui.graphics.vectormath.MatrixColumn column, int row);
- method public inline java.util.List<java.lang.Float> getM3storage();
- method public androidx.compose.ui.graphics.vectormath.Vector3 getX();
- method public androidx.compose.ui.graphics.vectormath.Vector3 getY();
- method public androidx.compose.ui.graphics.vectormath.Vector3 getZ();
- method public operator androidx.compose.ui.graphics.vectormath.Matrix3 inc();
- method public operator androidx.compose.ui.graphics.vectormath.Matrix3 minus(float v);
- method public operator androidx.compose.ui.graphics.vectormath.Matrix3 plus(float v);
- method public operator void set(int column, androidx.compose.ui.graphics.vectormath.Vector3 v);
- method public operator void set(int column, int row, float v);
- method public void setX(androidx.compose.ui.graphics.vectormath.Vector3 p);
- method public void setY(androidx.compose.ui.graphics.vectormath.Vector3 p);
- method public void setZ(androidx.compose.ui.graphics.vectormath.Vector3 p);
- method public operator androidx.compose.ui.graphics.vectormath.Matrix3 times(float v);
- method public operator androidx.compose.ui.graphics.vectormath.Matrix3 times(androidx.compose.ui.graphics.vectormath.Matrix3 m);
- method public operator androidx.compose.ui.graphics.vectormath.Vector3 times(androidx.compose.ui.graphics.vectormath.Vector3 v);
- method public float[] toFloatArray();
- method public operator androidx.compose.ui.graphics.vectormath.Matrix3 unaryMinus();
- property public final inline java.util.List<java.lang.Float> m3storage;
- field public static final androidx.compose.ui.graphics.vectormath.Matrix3.Companion Companion;
- }
-
- public static final class Matrix3.Companion {
- method public androidx.compose.ui.graphics.vectormath.Matrix3 identity();
- method public androidx.compose.ui.graphics.vectormath.Matrix3 of(float... a);
- }
-
- public final class Matrix4 {
- ctor public Matrix4(androidx.compose.ui.graphics.vectormath.Vector4 x, androidx.compose.ui.graphics.vectormath.Vector4 y, androidx.compose.ui.graphics.vectormath.Vector4 z, androidx.compose.ui.graphics.vectormath.Vector4 w);
- ctor public Matrix4();
- ctor public Matrix4(androidx.compose.ui.graphics.vectormath.Vector3 right, androidx.compose.ui.graphics.vectormath.Vector3 up, androidx.compose.ui.graphics.vectormath.Vector3 forward, androidx.compose.ui.graphics.vectormath.Vector3 position);
- ctor public Matrix4(androidx.compose.ui.graphics.vectormath.Matrix4 m);
- method public void assignColumns(androidx.compose.ui.graphics.vectormath.Matrix4 other);
- method public void assignFromStorage(java.util.List<java.lang.Float> storage);
- method public androidx.compose.ui.graphics.vectormath.Vector4 component1();
- method public androidx.compose.ui.graphics.vectormath.Vector4 component2();
- method public androidx.compose.ui.graphics.vectormath.Vector4 component3();
- method public androidx.compose.ui.graphics.vectormath.Vector4 component4();
- method public androidx.compose.ui.graphics.vectormath.Matrix4 copy(androidx.compose.ui.graphics.vectormath.Vector4 x, androidx.compose.ui.graphics.vectormath.Vector4 y, androidx.compose.ui.graphics.vectormath.Vector4 z, androidx.compose.ui.graphics.vectormath.Vector4 w);
- method public float copyInverse(androidx.compose.ui.graphics.vectormath.Matrix4 arg);
- method public operator androidx.compose.ui.graphics.vectormath.Matrix4 dec();
- method public operator androidx.compose.ui.graphics.vectormath.Matrix4 div(float v);
- method public operator androidx.compose.ui.graphics.vectormath.Vector4 get(int column);
- method public operator float get(int column, int row);
- method public operator androidx.compose.ui.graphics.vectormath.Vector4 get(androidx.compose.ui.graphics.vectormath.MatrixColumn column);
- method public operator float get(androidx.compose.ui.graphics.vectormath.MatrixColumn column, int row);
- method public float getDeterminant();
- method public inline androidx.compose.ui.graphics.vectormath.Vector3 getForward();
- method public inline java.util.List<java.lang.Float> getM4storage();
- method public inline androidx.compose.ui.graphics.vectormath.Vector3 getPosition();
- method public inline androidx.compose.ui.graphics.vectormath.Vector3 getRight();
- method public androidx.compose.ui.graphics.vectormath.Vector3 getRotation();
- method public androidx.compose.ui.graphics.vectormath.Vector4 getRow(int row);
- method public inline androidx.compose.ui.graphics.vectormath.Vector3 getScale();
- method public inline androidx.compose.ui.graphics.vectormath.Vector3 getTranslation();
- method public inline androidx.compose.ui.graphics.vectormath.Vector3 getUp();
- method public inline androidx.compose.ui.graphics.vectormath.Matrix3 getUpperLeft();
- method public androidx.compose.ui.graphics.vectormath.Vector4 getW();
- method public androidx.compose.ui.graphics.vectormath.Vector4 getX();
- method public androidx.compose.ui.graphics.vectormath.Vector4 getY();
- method public androidx.compose.ui.graphics.vectormath.Vector4 getZ();
- method public operator androidx.compose.ui.graphics.vectormath.Matrix4 inc();
- method public float invert();
- method public operator androidx.compose.ui.graphics.vectormath.Matrix4 minus(float v);
- method public androidx.compose.ui.graphics.vectormath.Vector3 perspectiveTransform(androidx.compose.ui.graphics.vectormath.Vector3 arg);
- method public operator androidx.compose.ui.graphics.vectormath.Matrix4 plus(float v);
- method public void rotateX(float radians);
- method public void rotateY(float radians);
- method public void rotateZ(float radians);
- method public void scale(Object x, Float? y = null, Float? z = null);
- method public operator void set(int column, androidx.compose.ui.graphics.vectormath.Vector4 v);
- method public operator void set(int column, int row, float v);
- method public inline void setForward(androidx.compose.ui.graphics.vectormath.Vector3 value);
- method public void setFrom(androidx.compose.ui.graphics.vectormath.Matrix4 arg);
- method public inline void setPosition(androidx.compose.ui.graphics.vectormath.Vector3 value);
- method public inline void setRight(androidx.compose.ui.graphics.vectormath.Vector3 value);
- method public void setTranslationRaw(float x, float y, float z);
- method public inline void setUp(androidx.compose.ui.graphics.vectormath.Vector3 value);
- method public void setW(androidx.compose.ui.graphics.vectormath.Vector4 p);
- method public void setX(androidx.compose.ui.graphics.vectormath.Vector4 p);
- method public void setY(androidx.compose.ui.graphics.vectormath.Vector4 p);
- method public void setZ(androidx.compose.ui.graphics.vectormath.Vector4 p);
- method public operator androidx.compose.ui.graphics.vectormath.Matrix4 times(float v);
- method public operator androidx.compose.ui.graphics.vectormath.Matrix4 times(androidx.compose.ui.graphics.vectormath.Matrix4 m);
- method public operator androidx.compose.ui.graphics.vectormath.Vector4 times(androidx.compose.ui.graphics.vectormath.Vector4 v);
- method public operator void timesAssign(androidx.compose.ui.graphics.vectormath.Matrix4 m);
- method public float[] toFloatArray();
- method public void translate(Object x, float y = 0.0f, float z = 0.0f);
- method public operator androidx.compose.ui.graphics.vectormath.Matrix4 unaryMinus();
- property public final float determinant;
- property public final inline androidx.compose.ui.graphics.vectormath.Vector3 forward;
- property public final inline java.util.List<java.lang.Float> m4storage;
- property public final inline androidx.compose.ui.graphics.vectormath.Vector3 position;
- property public final inline androidx.compose.ui.graphics.vectormath.Vector3 right;
- property public final androidx.compose.ui.graphics.vectormath.Vector3 rotation;
- property public final inline androidx.compose.ui.graphics.vectormath.Vector3 scale;
- property public final inline androidx.compose.ui.graphics.vectormath.Vector3 translation;
- property public final inline androidx.compose.ui.graphics.vectormath.Vector3 up;
- property public final inline androidx.compose.ui.graphics.vectormath.Matrix3 upperLeft;
- field public static final androidx.compose.ui.graphics.vectormath.Matrix4.Companion Companion;
- }
-
- public static final class Matrix4.Companion {
- method public androidx.compose.ui.graphics.vectormath.Matrix4 diagonal3(androidx.compose.ui.graphics.vectormath.Vector3 scale);
- method public androidx.compose.ui.graphics.vectormath.Matrix4 diagonal3Values(float x, float y, float z);
- method public androidx.compose.ui.graphics.vectormath.Matrix4 identity();
- method public androidx.compose.ui.graphics.vectormath.Matrix4 of(float... a);
- method public androidx.compose.ui.graphics.vectormath.Matrix4 rotationX(float radians);
- method public androidx.compose.ui.graphics.vectormath.Matrix4 rotationY(float radians);
- method public androidx.compose.ui.graphics.vectormath.Matrix4 rotationZ(float radians);
- method public androidx.compose.ui.graphics.vectormath.Matrix4 translation(androidx.compose.ui.graphics.vectormath.Vector3 translation);
- method public androidx.compose.ui.graphics.vectormath.Matrix4 translationValues(float x, float y, float z);
- method public androidx.compose.ui.graphics.vectormath.Matrix4 zero();
- }
-
- public final class Matrix4Kt {
- method public static Float? getAsScale(androidx.compose.ui.graphics.vectormath.Matrix4);
- method public static androidx.compose.ui.geometry.Offset? getAsTranslation(androidx.compose.ui.graphics.vectormath.Matrix4);
- method public static androidx.compose.ui.geometry.Rect inverseTransformRect(androidx.compose.ui.graphics.vectormath.Matrix4 transform, androidx.compose.ui.geometry.Rect rect);
- method public static boolean isIdentity(androidx.compose.ui.graphics.vectormath.Matrix4);
- method public static boolean matrixEquals(androidx.compose.ui.graphics.vectormath.Matrix4? a, androidx.compose.ui.graphics.vectormath.Matrix4? b);
- method public static long transformPoint-uF5TQos(androidx.compose.ui.graphics.vectormath.Matrix4, long point);
- method public static androidx.compose.ui.geometry.Rect transformRect(androidx.compose.ui.graphics.vectormath.Matrix4, androidx.compose.ui.geometry.Rect rect);
- }
-
- public enum MatrixColumn {
- method public static androidx.compose.ui.graphics.vectormath.MatrixColumn valueOf(String name) throws java.lang.IllegalArgumentException;
- method public static androidx.compose.ui.graphics.vectormath.MatrixColumn[] values();
- enum_constant public static final androidx.compose.ui.graphics.vectormath.MatrixColumn W;
- enum_constant public static final androidx.compose.ui.graphics.vectormath.MatrixColumn X;
- enum_constant public static final androidx.compose.ui.graphics.vectormath.MatrixColumn Y;
- enum_constant public static final androidx.compose.ui.graphics.vectormath.MatrixColumn Z;
- }
-
- public final class MatrixExtensionsKt {
- method public static androidx.compose.ui.graphics.vectormath.Matrix3 inverse(androidx.compose.ui.graphics.vectormath.Matrix3 m);
- method public static androidx.compose.ui.graphics.vectormath.Matrix4 inverse(androidx.compose.ui.graphics.vectormath.Matrix4 m);
- method public static androidx.compose.ui.graphics.vectormath.Matrix4 lookAt(androidx.compose.ui.graphics.vectormath.Vector3 eye, androidx.compose.ui.graphics.vectormath.Vector3 target, androidx.compose.ui.graphics.vectormath.Vector3 up = androidx.compose.ui.graphics.vectormath.Vector3(1.0));
- method public static androidx.compose.ui.graphics.vectormath.Matrix4 lookTowards(androidx.compose.ui.graphics.vectormath.Vector3 eye, androidx.compose.ui.graphics.vectormath.Vector3 forward, androidx.compose.ui.graphics.vectormath.Vector3 up = androidx.compose.ui.graphics.vectormath.Vector3(1.0));
- method public static androidx.compose.ui.graphics.vectormath.Matrix4 normal(androidx.compose.ui.graphics.vectormath.Matrix4 m);
- method public static androidx.compose.ui.graphics.vectormath.Matrix4 ortho(float l, float r, float b, float t, float n, float f);
- method public static androidx.compose.ui.graphics.vectormath.Matrix4 perspective(float fov, float ratio, float near, float far);
- method public static androidx.compose.ui.graphics.vectormath.Matrix4 rotation(androidx.compose.ui.graphics.vectormath.Matrix4 m);
- method public static androidx.compose.ui.graphics.vectormath.Matrix4 rotation(androidx.compose.ui.graphics.vectormath.Vector3 d);
- method public static androidx.compose.ui.graphics.vectormath.Matrix4 rotation(androidx.compose.ui.graphics.vectormath.Vector3 axis, float angle);
- method public static androidx.compose.ui.graphics.vectormath.Matrix4 scale(androidx.compose.ui.graphics.vectormath.Vector3 s);
- method public static androidx.compose.ui.graphics.vectormath.Matrix4 scale(androidx.compose.ui.graphics.vectormath.Matrix4 m);
- method public static androidx.compose.ui.graphics.vectormath.Matrix4 translation(androidx.compose.ui.graphics.vectormath.Vector3 t);
- method public static androidx.compose.ui.graphics.vectormath.Matrix4 translation(androidx.compose.ui.graphics.vectormath.Matrix4 m);
- method public static androidx.compose.ui.graphics.vectormath.Matrix3 transpose(androidx.compose.ui.graphics.vectormath.Matrix3 m);
- method public static androidx.compose.ui.graphics.vectormath.Matrix4 transpose(androidx.compose.ui.graphics.vectormath.Matrix4 m);
- }
-
- public final class ScalarKt {
- method public static inline float degrees(float v);
- method public static inline float radians(float v);
- field public static final float FOUR_PI = 12.566371f;
- field public static final float HALF_PI = 1.5707964f;
- field public static final float INV_FOUR_PI = 0.07957747f;
- field public static final float INV_PI = 0.31830987f;
- field public static final float INV_TWO_PI = 0.15915494f;
- field public static final float PI = 3.1415927f;
- field public static final float TWO_PI = 6.2831855f;
- }
-
- public final class Vector2 {
- ctor public Vector2(float x, float y);
- ctor public Vector2();
- ctor public Vector2(androidx.compose.ui.graphics.vectormath.Vector2 v);
- method public float component1();
- method public float component2();
- method public androidx.compose.ui.graphics.vectormath.Vector2 copy(float x, float y);
- method public operator androidx.compose.ui.graphics.vectormath.Vector2 dec();
- method public inline operator androidx.compose.ui.graphics.vectormath.Vector2 div(float v);
- method public inline operator androidx.compose.ui.graphics.vectormath.Vector2 div(androidx.compose.ui.graphics.vectormath.Vector2 v);
- method public operator float get(androidx.compose.ui.graphics.vectormath.VectorComponent index);
- method public operator androidx.compose.ui.graphics.vectormath.Vector2 get(androidx.compose.ui.graphics.vectormath.VectorComponent index1, androidx.compose.ui.graphics.vectormath.VectorComponent index2);
- method public operator float get(int index);
- method public operator androidx.compose.ui.graphics.vectormath.Vector2 get(int index1, int index2);
- method public inline float getG();
- method public inline float getR();
- method public inline androidx.compose.ui.graphics.vectormath.Vector2 getRg();
- method public inline float getS();
- method public inline androidx.compose.ui.graphics.vectormath.Vector2 getSt();
- method public inline float getT();
- method public inline java.util.List<java.lang.Float> getV2storage();
- method public float getX();
- method public inline androidx.compose.ui.graphics.vectormath.Vector2 getXy();
- method public float getY();
- method public operator androidx.compose.ui.graphics.vectormath.Vector2 inc();
- method public inline operator androidx.compose.ui.graphics.vectormath.Vector2 minus(float v);
- method public inline operator androidx.compose.ui.graphics.vectormath.Vector2 minus(androidx.compose.ui.graphics.vectormath.Vector2 v);
- method public inline operator androidx.compose.ui.graphics.vectormath.Vector2 plus(float v);
- method public inline operator androidx.compose.ui.graphics.vectormath.Vector2 plus(androidx.compose.ui.graphics.vectormath.Vector2 v);
- method public operator void set(int index, float v);
- method public operator void set(int index1, int index2, float v);
- method public operator void set(androidx.compose.ui.graphics.vectormath.VectorComponent index, float v);
- method public operator void set(androidx.compose.ui.graphics.vectormath.VectorComponent index1, androidx.compose.ui.graphics.vectormath.VectorComponent index2, float v);
- method public inline void setG(float value);
- method public inline void setR(float value);
- method public inline void setRg(androidx.compose.ui.graphics.vectormath.Vector2 value);
- method public inline void setS(float value);
- method public inline void setSt(androidx.compose.ui.graphics.vectormath.Vector2 value);
- method public inline void setT(float value);
- method public void setX(float p);
- method public inline void setXy(androidx.compose.ui.graphics.vectormath.Vector2 value);
- method public void setY(float p);
- method public inline operator androidx.compose.ui.graphics.vectormath.Vector2 times(float v);
- method public inline operator androidx.compose.ui.graphics.vectormath.Vector2 times(androidx.compose.ui.graphics.vectormath.Vector2 v);
- method public inline androidx.compose.ui.graphics.vectormath.Vector2 transform(kotlin.jvm.functions.Function1<? super java.lang.Float,java.lang.Float> block);
- method public operator androidx.compose.ui.graphics.vectormath.Vector2 unaryMinus();
- property public final inline float g;
- property public final inline float r;
- property public final inline androidx.compose.ui.graphics.vectormath.Vector2 rg;
- property public final inline float s;
- property public final inline androidx.compose.ui.graphics.vectormath.Vector2 st;
- property public final inline float t;
- property public final inline java.util.List<java.lang.Float> v2storage;
- property public final inline androidx.compose.ui.graphics.vectormath.Vector2 xy;
- }
-
- public final class Vector3 {
- ctor public Vector3(float x, float y, float z);
- ctor public Vector3();
- ctor public Vector3(androidx.compose.ui.graphics.vectormath.Vector2 v, float z);
- ctor public Vector3(androidx.compose.ui.graphics.vectormath.Vector3 v);
- method public float component1();
- method public float component2();
- method public float component3();
- method public androidx.compose.ui.graphics.vectormath.Vector3 copy(float x, float y, float z);
- method public operator androidx.compose.ui.graphics.vectormath.Vector3 dec();
- method public inline operator androidx.compose.ui.graphics.vectormath.Vector3 div(float v);
- method public inline operator androidx.compose.ui.graphics.vectormath.Vector3 div(androidx.compose.ui.graphics.vectormath.Vector2 v);
- method public inline operator androidx.compose.ui.graphics.vectormath.Vector3 div(androidx.compose.ui.graphics.vectormath.Vector3 v);
- method public operator float get(androidx.compose.ui.graphics.vectormath.VectorComponent index);
- method public operator androidx.compose.ui.graphics.vectormath.Vector2 get(androidx.compose.ui.graphics.vectormath.VectorComponent index1, androidx.compose.ui.graphics.vectormath.VectorComponent index2);
- method public operator androidx.compose.ui.graphics.vectormath.Vector3 get(androidx.compose.ui.graphics.vectormath.VectorComponent index1, androidx.compose.ui.graphics.vectormath.VectorComponent index2, androidx.compose.ui.graphics.vectormath.VectorComponent index3);
- method public operator float get(int index);
- method public operator androidx.compose.ui.graphics.vectormath.Vector2 get(int index1, int index2);
- method public operator androidx.compose.ui.graphics.vectormath.Vector3 get(int index1, int index2, int index3);
- method public inline float getB();
- method public inline float getG();
- method public inline float getP();
- method public inline float getR();
- method public inline androidx.compose.ui.graphics.vectormath.Vector2 getRg();
- method public inline androidx.compose.ui.graphics.vectormath.Vector3 getRgb();
- method public inline float getS();
- method public inline androidx.compose.ui.graphics.vectormath.Vector2 getSt();
- method public inline androidx.compose.ui.graphics.vectormath.Vector3 getStp();
- method public inline float getT();
- method public inline java.util.List<java.lang.Float> getV3storage();
- method public float getX();
- method public inline androidx.compose.ui.graphics.vectormath.Vector2 getXy();
- method public inline androidx.compose.ui.graphics.vectormath.Vector3 getXyz();
- method public float getY();
- method public float getZ();
- method public operator androidx.compose.ui.graphics.vectormath.Vector3 inc();
- method public inline operator androidx.compose.ui.graphics.vectormath.Vector3 minus(float v);
- method public inline operator androidx.compose.ui.graphics.vectormath.Vector3 minus(androidx.compose.ui.graphics.vectormath.Vector2 v);
- method public inline operator androidx.compose.ui.graphics.vectormath.Vector3 minus(androidx.compose.ui.graphics.vectormath.Vector3 v);
- method public inline operator androidx.compose.ui.graphics.vectormath.Vector3 plus(float v);
- method public inline operator androidx.compose.ui.graphics.vectormath.Vector3 plus(androidx.compose.ui.graphics.vectormath.Vector2 v);
- method public inline operator androidx.compose.ui.graphics.vectormath.Vector3 plus(androidx.compose.ui.graphics.vectormath.Vector3 v);
- method public operator void set(int index, float v);
- method public operator void set(int index1, int index2, float v);
- method public operator void set(int index1, int index2, int index3, float v);
- method public operator void set(androidx.compose.ui.graphics.vectormath.VectorComponent index, float v);
- method public operator void set(androidx.compose.ui.graphics.vectormath.VectorComponent index1, androidx.compose.ui.graphics.vectormath.VectorComponent index2, float v);
- method public operator void set(androidx.compose.ui.graphics.vectormath.VectorComponent index1, androidx.compose.ui.graphics.vectormath.VectorComponent index2, androidx.compose.ui.graphics.vectormath.VectorComponent index3, float v);
- method public inline void setB(float value);
- method public inline void setG(float value);
- method public inline void setP(float value);
- method public inline void setR(float value);
- method public inline void setRg(androidx.compose.ui.graphics.vectormath.Vector2 value);
- method public inline void setRgb(androidx.compose.ui.graphics.vectormath.Vector3 value);
- method public inline void setS(float value);
- method public inline void setSt(androidx.compose.ui.graphics.vectormath.Vector2 value);
- method public inline void setStp(androidx.compose.ui.graphics.vectormath.Vector3 value);
- method public inline void setT(float value);
- method public void setX(float p);
- method public inline void setXy(androidx.compose.ui.graphics.vectormath.Vector2 value);
- method public inline void setXyz(androidx.compose.ui.graphics.vectormath.Vector3 value);
- method public void setY(float p);
- method public void setZ(float p);
- method public inline operator androidx.compose.ui.graphics.vectormath.Vector3 times(float v);
- method public inline operator androidx.compose.ui.graphics.vectormath.Vector3 times(androidx.compose.ui.graphics.vectormath.Vector2 v);
- method public inline operator androidx.compose.ui.graphics.vectormath.Vector3 times(androidx.compose.ui.graphics.vectormath.Vector3 v);
- method public inline androidx.compose.ui.graphics.vectormath.Vector3 transform(kotlin.jvm.functions.Function1<? super java.lang.Float,java.lang.Float> block);
- method public operator androidx.compose.ui.graphics.vectormath.Vector3 unaryMinus();
- property public final inline float b;
- property public final inline float g;
- property public final inline float p;
- property public final inline float r;
- property public final inline androidx.compose.ui.graphics.vectormath.Vector2 rg;
- property public final inline androidx.compose.ui.graphics.vectormath.Vector3 rgb;
- property public final inline float s;
- property public final inline androidx.compose.ui.graphics.vectormath.Vector2 st;
- property public final inline androidx.compose.ui.graphics.vectormath.Vector3 stp;
- property public final inline float t;
- property public final inline java.util.List<java.lang.Float> v3storage;
- property public final inline androidx.compose.ui.graphics.vectormath.Vector2 xy;
- property public final inline androidx.compose.ui.graphics.vectormath.Vector3 xyz;
- }
-
- public final class Vector4 {
- ctor public Vector4(float x, float y, float z, float w);
- ctor public Vector4();
- ctor public Vector4(androidx.compose.ui.graphics.vectormath.Vector2 v, float z, float w);
- ctor public Vector4(androidx.compose.ui.graphics.vectormath.Vector3 v, float w);
- ctor public Vector4(androidx.compose.ui.graphics.vectormath.Vector4 v);
- method public void assignFromStorage(java.util.List<java.lang.Float> storage);
- method public float component1();
- method public float component2();
- method public float component3();
- method public float component4();
- method public androidx.compose.ui.graphics.vectormath.Vector4 copy(float x, float y, float z, float w);
- method public operator androidx.compose.ui.graphics.vectormath.Vector4 dec();
- method public inline operator androidx.compose.ui.graphics.vectormath.Vector4 div(float v);
- method public inline operator androidx.compose.ui.graphics.vectormath.Vector4 div(androidx.compose.ui.graphics.vectormath.Vector2 v);
- method public inline operator androidx.compose.ui.graphics.vectormath.Vector4 div(androidx.compose.ui.graphics.vectormath.Vector3 v);
- method public inline operator androidx.compose.ui.graphics.vectormath.Vector4 div(androidx.compose.ui.graphics.vectormath.Vector4 v);
- method public operator float get(androidx.compose.ui.graphics.vectormath.VectorComponent index);
- method public operator androidx.compose.ui.graphics.vectormath.Vector2 get(androidx.compose.ui.graphics.vectormath.VectorComponent index1, androidx.compose.ui.graphics.vectormath.VectorComponent index2);
- method public operator androidx.compose.ui.graphics.vectormath.Vector3 get(androidx.compose.ui.graphics.vectormath.VectorComponent index1, androidx.compose.ui.graphics.vectormath.VectorComponent index2, androidx.compose.ui.graphics.vectormath.VectorComponent index3);
- method public operator androidx.compose.ui.graphics.vectormath.Vector4 get(androidx.compose.ui.graphics.vectormath.VectorComponent index1, androidx.compose.ui.graphics.vectormath.VectorComponent index2, androidx.compose.ui.graphics.vectormath.VectorComponent index3, androidx.compose.ui.graphics.vectormath.VectorComponent index4);
- method public operator float get(int index);
- method public operator androidx.compose.ui.graphics.vectormath.Vector2 get(int index1, int index2);
- method public operator androidx.compose.ui.graphics.vectormath.Vector3 get(int index1, int index2, int index3);
- method public operator androidx.compose.ui.graphics.vectormath.Vector4 get(int index1, int index2, int index3, int index4);
- method public inline float getA();
- method public inline float getB();
- method public inline float getG();
- method public inline float getP();
- method public inline float getQ();
- method public inline float getR();
- method public inline androidx.compose.ui.graphics.vectormath.Vector2 getRg();
- method public inline androidx.compose.ui.graphics.vectormath.Vector3 getRgb();
- method public inline androidx.compose.ui.graphics.vectormath.Vector4 getRgba();
- method public inline float getS();
- method public inline androidx.compose.ui.graphics.vectormath.Vector2 getSt();
- method public inline androidx.compose.ui.graphics.vectormath.Vector3 getStp();
- method public inline androidx.compose.ui.graphics.vectormath.Vector4 getStpq();
- method public inline float getT();
- method public inline java.util.List<java.lang.Float> getV4storage();
- method public float getW();
- method public float getX();
- method public inline androidx.compose.ui.graphics.vectormath.Vector2 getXy();
- method public inline androidx.compose.ui.graphics.vectormath.Vector3 getXyz();
- method public inline androidx.compose.ui.graphics.vectormath.Vector4 getXyzw();
- method public float getY();
- method public float getZ();
- method public operator androidx.compose.ui.graphics.vectormath.Vector4 inc();
- method public inline operator androidx.compose.ui.graphics.vectormath.Vector4 minus(float v);
- method public inline operator androidx.compose.ui.graphics.vectormath.Vector4 minus(androidx.compose.ui.graphics.vectormath.Vector2 v);
- method public inline operator androidx.compose.ui.graphics.vectormath.Vector4 minus(androidx.compose.ui.graphics.vectormath.Vector3 v);
- method public inline operator androidx.compose.ui.graphics.vectormath.Vector4 minus(androidx.compose.ui.graphics.vectormath.Vector4 v);
- method public inline operator androidx.compose.ui.graphics.vectormath.Vector4 plus(float v);
- method public inline operator androidx.compose.ui.graphics.vectormath.Vector4 plus(androidx.compose.ui.graphics.vectormath.Vector2 v);
- method public inline operator androidx.compose.ui.graphics.vectormath.Vector4 plus(androidx.compose.ui.graphics.vectormath.Vector3 v);
- method public inline operator androidx.compose.ui.graphics.vectormath.Vector4 plus(androidx.compose.ui.graphics.vectormath.Vector4 v);
- method public operator void set(int index, float v);
- method public operator void set(int index1, int index2, float v);
- method public operator void set(int index1, int index2, int index3, float v);
- method public operator void set(int index1, int index2, int index3, int index4, float v);
- method public operator void set(androidx.compose.ui.graphics.vectormath.VectorComponent index, float v);
- method public operator void set(androidx.compose.ui.graphics.vectormath.VectorComponent index1, androidx.compose.ui.graphics.vectormath.VectorComponent index2, float v);
- method public operator void set(androidx.compose.ui.graphics.vectormath.VectorComponent index1, androidx.compose.ui.graphics.vectormath.VectorComponent index2, androidx.compose.ui.graphics.vectormath.VectorComponent index3, float v);
- method public operator void set(androidx.compose.ui.graphics.vectormath.VectorComponent index1, androidx.compose.ui.graphics.vectormath.VectorComponent index2, androidx.compose.ui.graphics.vectormath.VectorComponent index3, androidx.compose.ui.graphics.vectormath.VectorComponent index4, float v);
- method public inline void setA(float value);
- method public inline void setB(float value);
- method public inline void setG(float value);
- method public inline void setP(float value);
- method public inline void setQ(float value);
- method public inline void setR(float value);
- method public inline void setRg(androidx.compose.ui.graphics.vectormath.Vector2 value);
- method public inline void setRgb(androidx.compose.ui.graphics.vectormath.Vector3 value);
- method public inline void setRgba(androidx.compose.ui.graphics.vectormath.Vector4 value);
- method public inline void setS(float value);
- method public inline void setSt(androidx.compose.ui.graphics.vectormath.Vector2 value);
- method public inline void setStp(androidx.compose.ui.graphics.vectormath.Vector3 value);
- method public inline void setStpq(androidx.compose.ui.graphics.vectormath.Vector4 value);
- method public inline void setT(float value);
- method public void setW(float p);
- method public void setX(float p);
- method public inline void setXy(androidx.compose.ui.graphics.vectormath.Vector2 value);
- method public inline void setXyz(androidx.compose.ui.graphics.vectormath.Vector3 value);
- method public inline void setXyzw(androidx.compose.ui.graphics.vectormath.Vector4 value);
- method public void setY(float p);
- method public void setZ(float p);
- method public inline operator androidx.compose.ui.graphics.vectormath.Vector4 times(float v);
- method public inline operator androidx.compose.ui.graphics.vectormath.Vector4 times(androidx.compose.ui.graphics.vectormath.Vector2 v);
- method public inline operator androidx.compose.ui.graphics.vectormath.Vector4 times(androidx.compose.ui.graphics.vectormath.Vector3 v);
- method public inline operator androidx.compose.ui.graphics.vectormath.Vector4 times(androidx.compose.ui.graphics.vectormath.Vector4 v);
- method public inline androidx.compose.ui.graphics.vectormath.Vector4 transform(kotlin.jvm.functions.Function1<? super java.lang.Float,java.lang.Float> block);
- method public operator androidx.compose.ui.graphics.vectormath.Vector4 unaryMinus();
- property public final inline float a;
- property public final inline float b;
- property public final inline float g;
- property public final inline float p;
- property public final inline float q;
- property public final inline float r;
- property public final inline androidx.compose.ui.graphics.vectormath.Vector2 rg;
- property public final inline androidx.compose.ui.graphics.vectormath.Vector3 rgb;
- property public final inline androidx.compose.ui.graphics.vectormath.Vector4 rgba;
- property public final inline float s;
- property public final inline androidx.compose.ui.graphics.vectormath.Vector2 st;
- property public final inline androidx.compose.ui.graphics.vectormath.Vector3 stp;
- property public final inline androidx.compose.ui.graphics.vectormath.Vector4 stpq;
- property public final inline float t;
- property public final inline java.util.List<java.lang.Float> v4storage;
- property public final inline androidx.compose.ui.graphics.vectormath.Vector2 xy;
- property public final inline androidx.compose.ui.graphics.vectormath.Vector3 xyz;
- property public final inline androidx.compose.ui.graphics.vectormath.Vector4 xyzw;
- }
-
- public enum VectorComponent {
- method public static androidx.compose.ui.graphics.vectormath.VectorComponent valueOf(String name) throws java.lang.IllegalArgumentException;
- method public static androidx.compose.ui.graphics.vectormath.VectorComponent[] values();
- enum_constant public static final androidx.compose.ui.graphics.vectormath.VectorComponent A;
- enum_constant public static final androidx.compose.ui.graphics.vectormath.VectorComponent B;
- enum_constant public static final androidx.compose.ui.graphics.vectormath.VectorComponent G;
- enum_constant public static final androidx.compose.ui.graphics.vectormath.VectorComponent P;
- enum_constant public static final androidx.compose.ui.graphics.vectormath.VectorComponent Q;
- enum_constant public static final androidx.compose.ui.graphics.vectormath.VectorComponent R;
- enum_constant public static final androidx.compose.ui.graphics.vectormath.VectorComponent S;
- enum_constant public static final androidx.compose.ui.graphics.vectormath.VectorComponent T;
- enum_constant public static final androidx.compose.ui.graphics.vectormath.VectorComponent W;
- enum_constant public static final androidx.compose.ui.graphics.vectormath.VectorComponent X;
- enum_constant public static final androidx.compose.ui.graphics.vectormath.VectorComponent Y;
- enum_constant public static final androidx.compose.ui.graphics.vectormath.VectorComponent Z;
- }
-
- public final class VectorExtensionsKt {
- method public static inline androidx.compose.ui.graphics.vectormath.Vector2 abs(androidx.compose.ui.graphics.vectormath.Vector2 v);
- method public static inline androidx.compose.ui.graphics.vectormath.Vector3 abs(androidx.compose.ui.graphics.vectormath.Vector3 v);
- method public static inline androidx.compose.ui.graphics.vectormath.Vector4 abs(androidx.compose.ui.graphics.vectormath.Vector4 v);
- method public static inline androidx.compose.ui.graphics.vectormath.Vector2 coerceIn(androidx.compose.ui.graphics.vectormath.Vector2, float min, float max);
- method public static inline androidx.compose.ui.graphics.vectormath.Vector2 coerceIn(androidx.compose.ui.graphics.vectormath.Vector2, androidx.compose.ui.graphics.vectormath.Vector2 min, androidx.compose.ui.graphics.vectormath.Vector2 max);
- method public static inline androidx.compose.ui.graphics.vectormath.Vector3 coerceIn(androidx.compose.ui.graphics.vectormath.Vector3, float min, float max);
- method public static inline androidx.compose.ui.graphics.vectormath.Vector3 coerceIn(androidx.compose.ui.graphics.vectormath.Vector3, androidx.compose.ui.graphics.vectormath.Vector3 min, androidx.compose.ui.graphics.vectormath.Vector3 max);
- method public static inline androidx.compose.ui.graphics.vectormath.Vector4 coerceIn(androidx.compose.ui.graphics.vectormath.Vector4, float min, float max);
- method public static inline androidx.compose.ui.graphics.vectormath.Vector4 coerceIn(androidx.compose.ui.graphics.vectormath.Vector4, androidx.compose.ui.graphics.vectormath.Vector4 min, androidx.compose.ui.graphics.vectormath.Vector4 max);
- method public static inline androidx.compose.ui.graphics.vectormath.Vector3 cross(androidx.compose.ui.graphics.vectormath.Vector3 a, androidx.compose.ui.graphics.vectormath.Vector3 b);
- method public static inline float distance(androidx.compose.ui.graphics.vectormath.Vector2 a, androidx.compose.ui.graphics.vectormath.Vector2 b);
- method public static inline float distance(androidx.compose.ui.graphics.vectormath.Vector3 a, androidx.compose.ui.graphics.vectormath.Vector3 b);
- method public static inline float distance(androidx.compose.ui.graphics.vectormath.Vector4 a, androidx.compose.ui.graphics.vectormath.Vector4 b);
- method public static inline operator androidx.compose.ui.graphics.vectormath.Vector2 div(float, androidx.compose.ui.graphics.vectormath.Vector2 v);
- method public static inline operator androidx.compose.ui.graphics.vectormath.Vector3 div(float, androidx.compose.ui.graphics.vectormath.Vector3 v);
- method public static inline operator androidx.compose.ui.graphics.vectormath.Vector4 div(float, androidx.compose.ui.graphics.vectormath.Vector4 v);
- method public static inline float dot(androidx.compose.ui.graphics.vectormath.Vector2 a, androidx.compose.ui.graphics.vectormath.Vector2 b);
- method public static inline float dot(androidx.compose.ui.graphics.vectormath.Vector3 a, androidx.compose.ui.graphics.vectormath.Vector3 b);
- method public static inline float dot(androidx.compose.ui.graphics.vectormath.Vector4 a, androidx.compose.ui.graphics.vectormath.Vector4 b);
- method public static inline float length(androidx.compose.ui.graphics.vectormath.Vector2 v);
- method public static inline float length(androidx.compose.ui.graphics.vectormath.Vector3 v);
- method public static inline float length(androidx.compose.ui.graphics.vectormath.Vector4 v);
- method public static inline float length2(androidx.compose.ui.graphics.vectormath.Vector2 v);
- method public static inline float length2(androidx.compose.ui.graphics.vectormath.Vector3 v);
- method public static inline float length2(androidx.compose.ui.graphics.vectormath.Vector4 v);
- method public static inline float max(androidx.compose.ui.graphics.vectormath.Vector2 v);
- method public static inline androidx.compose.ui.graphics.vectormath.Vector2 max(androidx.compose.ui.graphics.vectormath.Vector2 a, androidx.compose.ui.graphics.vectormath.Vector2 b);
- method public static inline float max(androidx.compose.ui.graphics.vectormath.Vector3 v);
- method public static inline androidx.compose.ui.graphics.vectormath.Vector3 max(androidx.compose.ui.graphics.vectormath.Vector3 a, androidx.compose.ui.graphics.vectormath.Vector3 b);
- method public static inline float max(androidx.compose.ui.graphics.vectormath.Vector4 v);
- method public static inline androidx.compose.ui.graphics.vectormath.Vector4 max(androidx.compose.ui.graphics.vectormath.Vector4 a, androidx.compose.ui.graphics.vectormath.Vector4 b);
- method public static inline float min(androidx.compose.ui.graphics.vectormath.Vector2 v);
- method public static inline androidx.compose.ui.graphics.vectormath.Vector2 min(androidx.compose.ui.graphics.vectormath.Vector2 a, androidx.compose.ui.graphics.vectormath.Vector2 b);
- method public static inline float min(androidx.compose.ui.graphics.vectormath.Vector3 v);
- method public static inline androidx.compose.ui.graphics.vectormath.Vector3 min(androidx.compose.ui.graphics.vectormath.Vector3 a, androidx.compose.ui.graphics.vectormath.Vector3 b);
- method public static inline float min(androidx.compose.ui.graphics.vectormath.Vector4 v);
- method public static inline androidx.compose.ui.graphics.vectormath.Vector4 min(androidx.compose.ui.graphics.vectormath.Vector4 a, androidx.compose.ui.graphics.vectormath.Vector4 b);
- method public static inline operator androidx.compose.ui.graphics.vectormath.Vector2 minus(float, androidx.compose.ui.graphics.vectormath.Vector2 v);
- method public static inline operator androidx.compose.ui.graphics.vectormath.Vector3 minus(float, androidx.compose.ui.graphics.vectormath.Vector3 v);
- method public static inline operator androidx.compose.ui.graphics.vectormath.Vector4 minus(float, androidx.compose.ui.graphics.vectormath.Vector4 v);
- method public static androidx.compose.ui.graphics.vectormath.Vector2 normalize(androidx.compose.ui.graphics.vectormath.Vector2 v);
- method public static androidx.compose.ui.graphics.vectormath.Vector3 normalize(androidx.compose.ui.graphics.vectormath.Vector3 v);
- method public static androidx.compose.ui.graphics.vectormath.Vector4 normalize(androidx.compose.ui.graphics.vectormath.Vector4 v);
- method public static inline operator androidx.compose.ui.graphics.vectormath.Vector2 plus(float, androidx.compose.ui.graphics.vectormath.Vector2 v);
- method public static inline operator androidx.compose.ui.graphics.vectormath.Vector3 plus(float, androidx.compose.ui.graphics.vectormath.Vector3 v);
- method public static inline operator androidx.compose.ui.graphics.vectormath.Vector4 plus(float, androidx.compose.ui.graphics.vectormath.Vector4 v);
- method public static inline androidx.compose.ui.graphics.vectormath.Vector2 reflect(androidx.compose.ui.graphics.vectormath.Vector2 i, androidx.compose.ui.graphics.vectormath.Vector2 n);
- method public static inline androidx.compose.ui.graphics.vectormath.Vector3 reflect(androidx.compose.ui.graphics.vectormath.Vector3 i, androidx.compose.ui.graphics.vectormath.Vector3 n);
- method public static androidx.compose.ui.graphics.vectormath.Vector2 refract(androidx.compose.ui.graphics.vectormath.Vector2 i, androidx.compose.ui.graphics.vectormath.Vector2 n, float eta);
- method public static androidx.compose.ui.graphics.vectormath.Vector3 refract(androidx.compose.ui.graphics.vectormath.Vector3 i, androidx.compose.ui.graphics.vectormath.Vector3 n, float eta);
- method public static inline operator androidx.compose.ui.graphics.vectormath.Vector2 times(float, androidx.compose.ui.graphics.vectormath.Vector2 v);
- method public static inline operator androidx.compose.ui.graphics.vectormath.Vector3 times(float, androidx.compose.ui.graphics.vectormath.Vector3 v);
- method public static inline operator androidx.compose.ui.graphics.vectormath.Vector4 times(float, androidx.compose.ui.graphics.vectormath.Vector4 v);
- method public static inline androidx.compose.ui.graphics.vectormath.Vector2 transform(androidx.compose.ui.graphics.vectormath.Vector2 v, kotlin.jvm.functions.Function1<? super java.lang.Float,java.lang.Float> block);
- method public static inline androidx.compose.ui.graphics.vectormath.Vector3 transform(androidx.compose.ui.graphics.vectormath.Vector3 v, kotlin.jvm.functions.Function1<? super java.lang.Float,java.lang.Float> block);
- method public static inline androidx.compose.ui.graphics.vectormath.Vector4 transform(androidx.compose.ui.graphics.vectormath.Vector4 v, kotlin.jvm.functions.Function1<? super java.lang.Float,java.lang.Float> block);
- method public static inline infix androidx.compose.ui.graphics.vectormath.Vector3 x(androidx.compose.ui.graphics.vectormath.Vector3, androidx.compose.ui.graphics.vectormath.Vector3 v);
- }
-
-}
-
diff --git a/compose/ui/ui-graphics/api/restricted_current.txt b/compose/ui/ui-graphics/api/restricted_current.txt
index ad339904..f4808de 100644
--- a/compose/ui/ui-graphics/api/restricted_current.txt
+++ b/compose/ui/ui-graphics/api/restricted_current.txt
@@ -9,7 +9,7 @@
ctor public AndroidCanvas();
method public void clipPath(androidx.compose.ui.graphics.Path path, androidx.compose.ui.graphics.ClipOp clipOp);
method public void clipRect(float left, float top, float right, float bottom, androidx.compose.ui.graphics.ClipOp clipOp);
- method public void concat(androidx.compose.ui.graphics.vectormath.Matrix4 matrix4);
+ method public void concat-58bKbWc(float[] matrix);
method public void disableZ();
method public void drawArc(float left, float top, float right, float bottom, float startAngle, float sweepAngle, boolean useCenter, androidx.compose.ui.graphics.Paint paint);
method public void drawCircle-tVKstsI(long center, float radius, androidx.compose.ui.graphics.Paint paint);
@@ -46,6 +46,11 @@
method public static androidx.compose.ui.graphics.ImageAsset imageFromResource(android.content.res.Resources res, int resId);
}
+ public final class AndroidMatrixConversionsKt {
+ method public static void setFrom-7lL006A(float[], android.graphics.Matrix matrix);
+ method public static void setFrom-8AuSnpc(android.graphics.Matrix, float[] matrix);
+ }
+
public final class AndroidPaint implements androidx.compose.ui.graphics.Paint {
ctor public AndroidPaint();
method public android.graphics.Paint asFrameworkPaint();
@@ -208,7 +213,7 @@
method public void clipPath(androidx.compose.ui.graphics.Path path, androidx.compose.ui.graphics.ClipOp clipOp = androidx.compose.ui.graphics.ClipOp.Intersect);
method public default void clipRect(androidx.compose.ui.geometry.Rect rect, androidx.compose.ui.graphics.ClipOp clipOp = androidx.compose.ui.graphics.ClipOp.Intersect);
method public void clipRect(float left, float top, float right, float bottom, androidx.compose.ui.graphics.ClipOp clipOp = androidx.compose.ui.graphics.ClipOp.Intersect);
- method public void concat(androidx.compose.ui.graphics.vectormath.Matrix4 matrix4);
+ method public void concat-58bKbWc(float[] matrix);
method public void disableZ();
method public default void drawArc(androidx.compose.ui.geometry.Rect rect, float startAngle, float sweepAngle, boolean useCenter, androidx.compose.ui.graphics.Paint paint);
method public void drawArc(float left, float top, float right, float bottom, float startAngle, float sweepAngle, boolean useCenter, androidx.compose.ui.graphics.Paint paint);
@@ -338,6 +343,10 @@
method public static inline long useOrElse-iYUlWp8(long, kotlin.jvm.functions.Function0<androidx.compose.ui.graphics.Color> block);
}
+ public final class DegreesKt {
+ method @kotlin.PublishedApi internal static float degrees(float radians);
+ }
+
public enum FilterQuality {
method public static androidx.compose.ui.graphics.FilterQuality valueOf(String name) throws java.lang.IllegalArgumentException;
method public static androidx.compose.ui.graphics.FilterQuality[] values();
@@ -384,6 +393,34 @@
method @androidx.compose.runtime.Immutable public androidx.compose.ui.graphics.LinearGradient copy(java.util.List<androidx.compose.ui.graphics.Color> colors, java.util.List<java.lang.Float>? stops, float startX, float startY, float endX, float endY, androidx.compose.ui.graphics.TileMode tileMode);
}
+ public final inline class Matrix {
+ ctor public Matrix();
+ method public static float[]! constructor-impl(float[] values);
+ method public static inline boolean equals-impl(float[]! p, Object? p1);
+ method public static boolean equals-impl0(float[] p1, float[] p2);
+ method public static inline operator float get-impl(float[] $this, int row, int column);
+ method public float[] getValues();
+ method public static inline int hashCode-impl(float[]! p);
+ method public static void invert-impl(float[]! $this);
+ method public static androidx.compose.ui.geometry.Rect map-impl(float[] $this, androidx.compose.ui.geometry.Rect rect);
+ method public static void map-impl(float[] $this, androidx.compose.ui.geometry.MutableRect rect);
+ method public static long map-k-4lQ0M(float[] $this, long point);
+ method public static void reset-impl(float[]! $this);
+ method public static void rotateX-impl(float[] $this, float degrees);
+ method public static void rotateY-impl(float[] $this, float degrees);
+ method public static void rotateZ-impl(float[] $this, float degrees);
+ method public static void scale-impl(float[] $this, float x = 1f, float y = 1f, float z = 1f);
+ method public static inline operator void set-impl(float[] $this, int row, int column, float v);
+ method public static void setFrom-58bKbWc(float[] $this, float[] matrix);
+ method public static operator void timesAssign-58bKbWc(float[] $this, float[] m);
+ method public static String toString-impl(float[]! $this);
+ method public static void translate-impl(float[] $this, float x = 0f, float y = 0f, float z = 0f);
+ }
+
+ public final class MatrixKt {
+ method public static boolean isIdentity-58bKbWc(float[]);
+ }
+
public abstract sealed class Outline {
}
@@ -902,7 +939,7 @@
method public void inset(float left, float top, float right, float bottom);
method public void rotate(float degrees, float pivotX = center.x, float pivotY = center.y);
method public void scale(float scaleX, float scaleY = scaleX, float pivotX = center.x, float pivotY = center.y);
- method public void transform(androidx.compose.ui.graphics.vectormath.Matrix4 matrix);
+ method public void transform-58bKbWc(float[] matrix);
method public void translate(float left = 0.0f, float top = 0.0f);
property public default long center;
property public abstract long size;
@@ -1231,512 +1268,3 @@
}
-package androidx.compose.ui.graphics.vectormath {
-
- public final class Matrix3 {
- ctor public Matrix3(androidx.compose.ui.graphics.vectormath.Vector3 x, androidx.compose.ui.graphics.vectormath.Vector3 y, androidx.compose.ui.graphics.vectormath.Vector3 z);
- ctor public Matrix3();
- ctor public Matrix3(androidx.compose.ui.graphics.vectormath.Matrix3 m);
- method public androidx.compose.ui.graphics.vectormath.Vector3 component1();
- method public androidx.compose.ui.graphics.vectormath.Vector3 component2();
- method public androidx.compose.ui.graphics.vectormath.Vector3 component3();
- method public androidx.compose.ui.graphics.vectormath.Matrix3 copy(androidx.compose.ui.graphics.vectormath.Vector3 x, androidx.compose.ui.graphics.vectormath.Vector3 y, androidx.compose.ui.graphics.vectormath.Vector3 z);
- method public operator androidx.compose.ui.graphics.vectormath.Matrix3 dec();
- method public operator androidx.compose.ui.graphics.vectormath.Matrix3 div(float v);
- method public operator androidx.compose.ui.graphics.vectormath.Vector3 get(int column);
- method public operator float get(int column, int row);
- method public operator androidx.compose.ui.graphics.vectormath.Vector3 get(androidx.compose.ui.graphics.vectormath.MatrixColumn column);
- method public operator float get(androidx.compose.ui.graphics.vectormath.MatrixColumn column, int row);
- method public inline java.util.List<java.lang.Float> getM3storage();
- method public androidx.compose.ui.graphics.vectormath.Vector3 getX();
- method public androidx.compose.ui.graphics.vectormath.Vector3 getY();
- method public androidx.compose.ui.graphics.vectormath.Vector3 getZ();
- method public operator androidx.compose.ui.graphics.vectormath.Matrix3 inc();
- method public operator androidx.compose.ui.graphics.vectormath.Matrix3 minus(float v);
- method public operator androidx.compose.ui.graphics.vectormath.Matrix3 plus(float v);
- method public operator void set(int column, androidx.compose.ui.graphics.vectormath.Vector3 v);
- method public operator void set(int column, int row, float v);
- method public void setX(androidx.compose.ui.graphics.vectormath.Vector3 p);
- method public void setY(androidx.compose.ui.graphics.vectormath.Vector3 p);
- method public void setZ(androidx.compose.ui.graphics.vectormath.Vector3 p);
- method public operator androidx.compose.ui.graphics.vectormath.Matrix3 times(float v);
- method public operator androidx.compose.ui.graphics.vectormath.Matrix3 times(androidx.compose.ui.graphics.vectormath.Matrix3 m);
- method public operator androidx.compose.ui.graphics.vectormath.Vector3 times(androidx.compose.ui.graphics.vectormath.Vector3 v);
- method public float[] toFloatArray();
- method public operator androidx.compose.ui.graphics.vectormath.Matrix3 unaryMinus();
- property public final inline java.util.List<java.lang.Float> m3storage;
- field public static final androidx.compose.ui.graphics.vectormath.Matrix3.Companion Companion;
- }
-
- public static final class Matrix3.Companion {
- method public androidx.compose.ui.graphics.vectormath.Matrix3 identity();
- method public androidx.compose.ui.graphics.vectormath.Matrix3 of(float... a);
- }
-
- public final class Matrix4 {
- ctor public Matrix4(androidx.compose.ui.graphics.vectormath.Vector4 x, androidx.compose.ui.graphics.vectormath.Vector4 y, androidx.compose.ui.graphics.vectormath.Vector4 z, androidx.compose.ui.graphics.vectormath.Vector4 w);
- ctor public Matrix4();
- ctor public Matrix4(androidx.compose.ui.graphics.vectormath.Vector3 right, androidx.compose.ui.graphics.vectormath.Vector3 up, androidx.compose.ui.graphics.vectormath.Vector3 forward, androidx.compose.ui.graphics.vectormath.Vector3 position);
- ctor public Matrix4(androidx.compose.ui.graphics.vectormath.Matrix4 m);
- method public void assignColumns(androidx.compose.ui.graphics.vectormath.Matrix4 other);
- method public void assignFromStorage(java.util.List<java.lang.Float> storage);
- method public androidx.compose.ui.graphics.vectormath.Vector4 component1();
- method public androidx.compose.ui.graphics.vectormath.Vector4 component2();
- method public androidx.compose.ui.graphics.vectormath.Vector4 component3();
- method public androidx.compose.ui.graphics.vectormath.Vector4 component4();
- method public androidx.compose.ui.graphics.vectormath.Matrix4 copy(androidx.compose.ui.graphics.vectormath.Vector4 x, androidx.compose.ui.graphics.vectormath.Vector4 y, androidx.compose.ui.graphics.vectormath.Vector4 z, androidx.compose.ui.graphics.vectormath.Vector4 w);
- method public float copyInverse(androidx.compose.ui.graphics.vectormath.Matrix4 arg);
- method public operator androidx.compose.ui.graphics.vectormath.Matrix4 dec();
- method public operator androidx.compose.ui.graphics.vectormath.Matrix4 div(float v);
- method public operator androidx.compose.ui.graphics.vectormath.Vector4 get(int column);
- method public operator float get(int column, int row);
- method public operator androidx.compose.ui.graphics.vectormath.Vector4 get(androidx.compose.ui.graphics.vectormath.MatrixColumn column);
- method public operator float get(androidx.compose.ui.graphics.vectormath.MatrixColumn column, int row);
- method public float getDeterminant();
- method public inline androidx.compose.ui.graphics.vectormath.Vector3 getForward();
- method public inline java.util.List<java.lang.Float> getM4storage();
- method public inline androidx.compose.ui.graphics.vectormath.Vector3 getPosition();
- method public inline androidx.compose.ui.graphics.vectormath.Vector3 getRight();
- method public androidx.compose.ui.graphics.vectormath.Vector3 getRotation();
- method public androidx.compose.ui.graphics.vectormath.Vector4 getRow(int row);
- method public inline androidx.compose.ui.graphics.vectormath.Vector3 getScale();
- method public inline androidx.compose.ui.graphics.vectormath.Vector3 getTranslation();
- method public inline androidx.compose.ui.graphics.vectormath.Vector3 getUp();
- method public inline androidx.compose.ui.graphics.vectormath.Matrix3 getUpperLeft();
- method public androidx.compose.ui.graphics.vectormath.Vector4 getW();
- method public androidx.compose.ui.graphics.vectormath.Vector4 getX();
- method public androidx.compose.ui.graphics.vectormath.Vector4 getY();
- method public androidx.compose.ui.graphics.vectormath.Vector4 getZ();
- method public operator androidx.compose.ui.graphics.vectormath.Matrix4 inc();
- method public float invert();
- method public operator androidx.compose.ui.graphics.vectormath.Matrix4 minus(float v);
- method public androidx.compose.ui.graphics.vectormath.Vector3 perspectiveTransform(androidx.compose.ui.graphics.vectormath.Vector3 arg);
- method public operator androidx.compose.ui.graphics.vectormath.Matrix4 plus(float v);
- method public void rotateX(float radians);
- method public void rotateY(float radians);
- method public void rotateZ(float radians);
- method public void scale(Object x, Float? y = null, Float? z = null);
- method public operator void set(int column, androidx.compose.ui.graphics.vectormath.Vector4 v);
- method public operator void set(int column, int row, float v);
- method public inline void setForward(androidx.compose.ui.graphics.vectormath.Vector3 value);
- method public void setFrom(androidx.compose.ui.graphics.vectormath.Matrix4 arg);
- method public inline void setPosition(androidx.compose.ui.graphics.vectormath.Vector3 value);
- method public inline void setRight(androidx.compose.ui.graphics.vectormath.Vector3 value);
- method public void setTranslationRaw(float x, float y, float z);
- method public inline void setUp(androidx.compose.ui.graphics.vectormath.Vector3 value);
- method public void setW(androidx.compose.ui.graphics.vectormath.Vector4 p);
- method public void setX(androidx.compose.ui.graphics.vectormath.Vector4 p);
- method public void setY(androidx.compose.ui.graphics.vectormath.Vector4 p);
- method public void setZ(androidx.compose.ui.graphics.vectormath.Vector4 p);
- method public operator androidx.compose.ui.graphics.vectormath.Matrix4 times(float v);
- method public operator androidx.compose.ui.graphics.vectormath.Matrix4 times(androidx.compose.ui.graphics.vectormath.Matrix4 m);
- method public operator androidx.compose.ui.graphics.vectormath.Vector4 times(androidx.compose.ui.graphics.vectormath.Vector4 v);
- method public operator void timesAssign(androidx.compose.ui.graphics.vectormath.Matrix4 m);
- method public float[] toFloatArray();
- method public void translate(Object x, float y = 0.0f, float z = 0.0f);
- method public operator androidx.compose.ui.graphics.vectormath.Matrix4 unaryMinus();
- property public final float determinant;
- property public final inline androidx.compose.ui.graphics.vectormath.Vector3 forward;
- property public final inline java.util.List<java.lang.Float> m4storage;
- property public final inline androidx.compose.ui.graphics.vectormath.Vector3 position;
- property public final inline androidx.compose.ui.graphics.vectormath.Vector3 right;
- property public final androidx.compose.ui.graphics.vectormath.Vector3 rotation;
- property public final inline androidx.compose.ui.graphics.vectormath.Vector3 scale;
- property public final inline androidx.compose.ui.graphics.vectormath.Vector3 translation;
- property public final inline androidx.compose.ui.graphics.vectormath.Vector3 up;
- property public final inline androidx.compose.ui.graphics.vectormath.Matrix3 upperLeft;
- field public static final androidx.compose.ui.graphics.vectormath.Matrix4.Companion Companion;
- }
-
- public static final class Matrix4.Companion {
- method public androidx.compose.ui.graphics.vectormath.Matrix4 diagonal3(androidx.compose.ui.graphics.vectormath.Vector3 scale);
- method public androidx.compose.ui.graphics.vectormath.Matrix4 diagonal3Values(float x, float y, float z);
- method public androidx.compose.ui.graphics.vectormath.Matrix4 identity();
- method public androidx.compose.ui.graphics.vectormath.Matrix4 of(float... a);
- method public androidx.compose.ui.graphics.vectormath.Matrix4 rotationX(float radians);
- method public androidx.compose.ui.graphics.vectormath.Matrix4 rotationY(float radians);
- method public androidx.compose.ui.graphics.vectormath.Matrix4 rotationZ(float radians);
- method public androidx.compose.ui.graphics.vectormath.Matrix4 translation(androidx.compose.ui.graphics.vectormath.Vector3 translation);
- method public androidx.compose.ui.graphics.vectormath.Matrix4 translationValues(float x, float y, float z);
- method public androidx.compose.ui.graphics.vectormath.Matrix4 zero();
- }
-
- public final class Matrix4Kt {
- method public static Float? getAsScale(androidx.compose.ui.graphics.vectormath.Matrix4);
- method public static androidx.compose.ui.geometry.Offset? getAsTranslation(androidx.compose.ui.graphics.vectormath.Matrix4);
- method public static androidx.compose.ui.geometry.Rect inverseTransformRect(androidx.compose.ui.graphics.vectormath.Matrix4 transform, androidx.compose.ui.geometry.Rect rect);
- method public static boolean isIdentity(androidx.compose.ui.graphics.vectormath.Matrix4);
- method public static boolean matrixEquals(androidx.compose.ui.graphics.vectormath.Matrix4? a, androidx.compose.ui.graphics.vectormath.Matrix4? b);
- method public static long transformPoint-uF5TQos(androidx.compose.ui.graphics.vectormath.Matrix4, long point);
- method public static androidx.compose.ui.geometry.Rect transformRect(androidx.compose.ui.graphics.vectormath.Matrix4, androidx.compose.ui.geometry.Rect rect);
- }
-
- public enum MatrixColumn {
- method public static androidx.compose.ui.graphics.vectormath.MatrixColumn valueOf(String name) throws java.lang.IllegalArgumentException;
- method public static androidx.compose.ui.graphics.vectormath.MatrixColumn[] values();
- enum_constant public static final androidx.compose.ui.graphics.vectormath.MatrixColumn W;
- enum_constant public static final androidx.compose.ui.graphics.vectormath.MatrixColumn X;
- enum_constant public static final androidx.compose.ui.graphics.vectormath.MatrixColumn Y;
- enum_constant public static final androidx.compose.ui.graphics.vectormath.MatrixColumn Z;
- }
-
- public final class MatrixExtensionsKt {
- method public static androidx.compose.ui.graphics.vectormath.Matrix3 inverse(androidx.compose.ui.graphics.vectormath.Matrix3 m);
- method public static androidx.compose.ui.graphics.vectormath.Matrix4 inverse(androidx.compose.ui.graphics.vectormath.Matrix4 m);
- method public static androidx.compose.ui.graphics.vectormath.Matrix4 lookAt(androidx.compose.ui.graphics.vectormath.Vector3 eye, androidx.compose.ui.graphics.vectormath.Vector3 target, androidx.compose.ui.graphics.vectormath.Vector3 up = androidx.compose.ui.graphics.vectormath.Vector3(1.0));
- method public static androidx.compose.ui.graphics.vectormath.Matrix4 lookTowards(androidx.compose.ui.graphics.vectormath.Vector3 eye, androidx.compose.ui.graphics.vectormath.Vector3 forward, androidx.compose.ui.graphics.vectormath.Vector3 up = androidx.compose.ui.graphics.vectormath.Vector3(1.0));
- method public static androidx.compose.ui.graphics.vectormath.Matrix4 normal(androidx.compose.ui.graphics.vectormath.Matrix4 m);
- method public static androidx.compose.ui.graphics.vectormath.Matrix4 ortho(float l, float r, float b, float t, float n, float f);
- method public static androidx.compose.ui.graphics.vectormath.Matrix4 perspective(float fov, float ratio, float near, float far);
- method public static androidx.compose.ui.graphics.vectormath.Matrix4 rotation(androidx.compose.ui.graphics.vectormath.Matrix4 m);
- method public static androidx.compose.ui.graphics.vectormath.Matrix4 rotation(androidx.compose.ui.graphics.vectormath.Vector3 d);
- method public static androidx.compose.ui.graphics.vectormath.Matrix4 rotation(androidx.compose.ui.graphics.vectormath.Vector3 axis, float angle);
- method public static androidx.compose.ui.graphics.vectormath.Matrix4 scale(androidx.compose.ui.graphics.vectormath.Vector3 s);
- method public static androidx.compose.ui.graphics.vectormath.Matrix4 scale(androidx.compose.ui.graphics.vectormath.Matrix4 m);
- method public static androidx.compose.ui.graphics.vectormath.Matrix4 translation(androidx.compose.ui.graphics.vectormath.Vector3 t);
- method public static androidx.compose.ui.graphics.vectormath.Matrix4 translation(androidx.compose.ui.graphics.vectormath.Matrix4 m);
- method public static androidx.compose.ui.graphics.vectormath.Matrix3 transpose(androidx.compose.ui.graphics.vectormath.Matrix3 m);
- method public static androidx.compose.ui.graphics.vectormath.Matrix4 transpose(androidx.compose.ui.graphics.vectormath.Matrix4 m);
- }
-
- public final class ScalarKt {
- method public static inline float degrees(float v);
- method public static inline float radians(float v);
- field public static final float FOUR_PI = 12.566371f;
- field public static final float HALF_PI = 1.5707964f;
- field public static final float INV_FOUR_PI = 0.07957747f;
- field public static final float INV_PI = 0.31830987f;
- field public static final float INV_TWO_PI = 0.15915494f;
- field public static final float PI = 3.1415927f;
- field public static final float TWO_PI = 6.2831855f;
- }
-
- public final class Vector2 {
- ctor public Vector2(float x, float y);
- ctor public Vector2();
- ctor public Vector2(androidx.compose.ui.graphics.vectormath.Vector2 v);
- method public float component1();
- method public float component2();
- method public androidx.compose.ui.graphics.vectormath.Vector2 copy(float x, float y);
- method public operator androidx.compose.ui.graphics.vectormath.Vector2 dec();
- method public inline operator androidx.compose.ui.graphics.vectormath.Vector2 div(float v);
- method public inline operator androidx.compose.ui.graphics.vectormath.Vector2 div(androidx.compose.ui.graphics.vectormath.Vector2 v);
- method public operator float get(androidx.compose.ui.graphics.vectormath.VectorComponent index);
- method public operator androidx.compose.ui.graphics.vectormath.Vector2 get(androidx.compose.ui.graphics.vectormath.VectorComponent index1, androidx.compose.ui.graphics.vectormath.VectorComponent index2);
- method public operator float get(int index);
- method public operator androidx.compose.ui.graphics.vectormath.Vector2 get(int index1, int index2);
- method public inline float getG();
- method public inline float getR();
- method public inline androidx.compose.ui.graphics.vectormath.Vector2 getRg();
- method public inline float getS();
- method public inline androidx.compose.ui.graphics.vectormath.Vector2 getSt();
- method public inline float getT();
- method public inline java.util.List<java.lang.Float> getV2storage();
- method public float getX();
- method public inline androidx.compose.ui.graphics.vectormath.Vector2 getXy();
- method public float getY();
- method public operator androidx.compose.ui.graphics.vectormath.Vector2 inc();
- method public inline operator androidx.compose.ui.graphics.vectormath.Vector2 minus(float v);
- method public inline operator androidx.compose.ui.graphics.vectormath.Vector2 minus(androidx.compose.ui.graphics.vectormath.Vector2 v);
- method public inline operator androidx.compose.ui.graphics.vectormath.Vector2 plus(float v);
- method public inline operator androidx.compose.ui.graphics.vectormath.Vector2 plus(androidx.compose.ui.graphics.vectormath.Vector2 v);
- method public operator void set(int index, float v);
- method public operator void set(int index1, int index2, float v);
- method public operator void set(androidx.compose.ui.graphics.vectormath.VectorComponent index, float v);
- method public operator void set(androidx.compose.ui.graphics.vectormath.VectorComponent index1, androidx.compose.ui.graphics.vectormath.VectorComponent index2, float v);
- method public inline void setG(float value);
- method public inline void setR(float value);
- method public inline void setRg(androidx.compose.ui.graphics.vectormath.Vector2 value);
- method public inline void setS(float value);
- method public inline void setSt(androidx.compose.ui.graphics.vectormath.Vector2 value);
- method public inline void setT(float value);
- method public void setX(float p);
- method public inline void setXy(androidx.compose.ui.graphics.vectormath.Vector2 value);
- method public void setY(float p);
- method public inline operator androidx.compose.ui.graphics.vectormath.Vector2 times(float v);
- method public inline operator androidx.compose.ui.graphics.vectormath.Vector2 times(androidx.compose.ui.graphics.vectormath.Vector2 v);
- method public inline androidx.compose.ui.graphics.vectormath.Vector2 transform(kotlin.jvm.functions.Function1<? super java.lang.Float,java.lang.Float> block);
- method public operator androidx.compose.ui.graphics.vectormath.Vector2 unaryMinus();
- property public final inline float g;
- property public final inline float r;
- property public final inline androidx.compose.ui.graphics.vectormath.Vector2 rg;
- property public final inline float s;
- property public final inline androidx.compose.ui.graphics.vectormath.Vector2 st;
- property public final inline float t;
- property public final inline java.util.List<java.lang.Float> v2storage;
- property public final inline androidx.compose.ui.graphics.vectormath.Vector2 xy;
- }
-
- public final class Vector3 {
- ctor public Vector3(float x, float y, float z);
- ctor public Vector3();
- ctor public Vector3(androidx.compose.ui.graphics.vectormath.Vector2 v, float z);
- ctor public Vector3(androidx.compose.ui.graphics.vectormath.Vector3 v);
- method public float component1();
- method public float component2();
- method public float component3();
- method public androidx.compose.ui.graphics.vectormath.Vector3 copy(float x, float y, float z);
- method public operator androidx.compose.ui.graphics.vectormath.Vector3 dec();
- method public inline operator androidx.compose.ui.graphics.vectormath.Vector3 div(float v);
- method public inline operator androidx.compose.ui.graphics.vectormath.Vector3 div(androidx.compose.ui.graphics.vectormath.Vector2 v);
- method public inline operator androidx.compose.ui.graphics.vectormath.Vector3 div(androidx.compose.ui.graphics.vectormath.Vector3 v);
- method public operator float get(androidx.compose.ui.graphics.vectormath.VectorComponent index);
- method public operator androidx.compose.ui.graphics.vectormath.Vector2 get(androidx.compose.ui.graphics.vectormath.VectorComponent index1, androidx.compose.ui.graphics.vectormath.VectorComponent index2);
- method public operator androidx.compose.ui.graphics.vectormath.Vector3 get(androidx.compose.ui.graphics.vectormath.VectorComponent index1, androidx.compose.ui.graphics.vectormath.VectorComponent index2, androidx.compose.ui.graphics.vectormath.VectorComponent index3);
- method public operator float get(int index);
- method public operator androidx.compose.ui.graphics.vectormath.Vector2 get(int index1, int index2);
- method public operator androidx.compose.ui.graphics.vectormath.Vector3 get(int index1, int index2, int index3);
- method public inline float getB();
- method public inline float getG();
- method public inline float getP();
- method public inline float getR();
- method public inline androidx.compose.ui.graphics.vectormath.Vector2 getRg();
- method public inline androidx.compose.ui.graphics.vectormath.Vector3 getRgb();
- method public inline float getS();
- method public inline androidx.compose.ui.graphics.vectormath.Vector2 getSt();
- method public inline androidx.compose.ui.graphics.vectormath.Vector3 getStp();
- method public inline float getT();
- method public inline java.util.List<java.lang.Float> getV3storage();
- method public float getX();
- method public inline androidx.compose.ui.graphics.vectormath.Vector2 getXy();
- method public inline androidx.compose.ui.graphics.vectormath.Vector3 getXyz();
- method public float getY();
- method public float getZ();
- method public operator androidx.compose.ui.graphics.vectormath.Vector3 inc();
- method public inline operator androidx.compose.ui.graphics.vectormath.Vector3 minus(float v);
- method public inline operator androidx.compose.ui.graphics.vectormath.Vector3 minus(androidx.compose.ui.graphics.vectormath.Vector2 v);
- method public inline operator androidx.compose.ui.graphics.vectormath.Vector3 minus(androidx.compose.ui.graphics.vectormath.Vector3 v);
- method public inline operator androidx.compose.ui.graphics.vectormath.Vector3 plus(float v);
- method public inline operator androidx.compose.ui.graphics.vectormath.Vector3 plus(androidx.compose.ui.graphics.vectormath.Vector2 v);
- method public inline operator androidx.compose.ui.graphics.vectormath.Vector3 plus(androidx.compose.ui.graphics.vectormath.Vector3 v);
- method public operator void set(int index, float v);
- method public operator void set(int index1, int index2, float v);
- method public operator void set(int index1, int index2, int index3, float v);
- method public operator void set(androidx.compose.ui.graphics.vectormath.VectorComponent index, float v);
- method public operator void set(androidx.compose.ui.graphics.vectormath.VectorComponent index1, androidx.compose.ui.graphics.vectormath.VectorComponent index2, float v);
- method public operator void set(androidx.compose.ui.graphics.vectormath.VectorComponent index1, androidx.compose.ui.graphics.vectormath.VectorComponent index2, androidx.compose.ui.graphics.vectormath.VectorComponent index3, float v);
- method public inline void setB(float value);
- method public inline void setG(float value);
- method public inline void setP(float value);
- method public inline void setR(float value);
- method public inline void setRg(androidx.compose.ui.graphics.vectormath.Vector2 value);
- method public inline void setRgb(androidx.compose.ui.graphics.vectormath.Vector3 value);
- method public inline void setS(float value);
- method public inline void setSt(androidx.compose.ui.graphics.vectormath.Vector2 value);
- method public inline void setStp(androidx.compose.ui.graphics.vectormath.Vector3 value);
- method public inline void setT(float value);
- method public void setX(float p);
- method public inline void setXy(androidx.compose.ui.graphics.vectormath.Vector2 value);
- method public inline void setXyz(androidx.compose.ui.graphics.vectormath.Vector3 value);
- method public void setY(float p);
- method public void setZ(float p);
- method public inline operator androidx.compose.ui.graphics.vectormath.Vector3 times(float v);
- method public inline operator androidx.compose.ui.graphics.vectormath.Vector3 times(androidx.compose.ui.graphics.vectormath.Vector2 v);
- method public inline operator androidx.compose.ui.graphics.vectormath.Vector3 times(androidx.compose.ui.graphics.vectormath.Vector3 v);
- method public inline androidx.compose.ui.graphics.vectormath.Vector3 transform(kotlin.jvm.functions.Function1<? super java.lang.Float,java.lang.Float> block);
- method public operator androidx.compose.ui.graphics.vectormath.Vector3 unaryMinus();
- property public final inline float b;
- property public final inline float g;
- property public final inline float p;
- property public final inline float r;
- property public final inline androidx.compose.ui.graphics.vectormath.Vector2 rg;
- property public final inline androidx.compose.ui.graphics.vectormath.Vector3 rgb;
- property public final inline float s;
- property public final inline androidx.compose.ui.graphics.vectormath.Vector2 st;
- property public final inline androidx.compose.ui.graphics.vectormath.Vector3 stp;
- property public final inline float t;
- property public final inline java.util.List<java.lang.Float> v3storage;
- property public final inline androidx.compose.ui.graphics.vectormath.Vector2 xy;
- property public final inline androidx.compose.ui.graphics.vectormath.Vector3 xyz;
- }
-
- public final class Vector4 {
- ctor public Vector4(float x, float y, float z, float w);
- ctor public Vector4();
- ctor public Vector4(androidx.compose.ui.graphics.vectormath.Vector2 v, float z, float w);
- ctor public Vector4(androidx.compose.ui.graphics.vectormath.Vector3 v, float w);
- ctor public Vector4(androidx.compose.ui.graphics.vectormath.Vector4 v);
- method public void assignFromStorage(java.util.List<java.lang.Float> storage);
- method public float component1();
- method public float component2();
- method public float component3();
- method public float component4();
- method public androidx.compose.ui.graphics.vectormath.Vector4 copy(float x, float y, float z, float w);
- method public operator androidx.compose.ui.graphics.vectormath.Vector4 dec();
- method public inline operator androidx.compose.ui.graphics.vectormath.Vector4 div(float v);
- method public inline operator androidx.compose.ui.graphics.vectormath.Vector4 div(androidx.compose.ui.graphics.vectormath.Vector2 v);
- method public inline operator androidx.compose.ui.graphics.vectormath.Vector4 div(androidx.compose.ui.graphics.vectormath.Vector3 v);
- method public inline operator androidx.compose.ui.graphics.vectormath.Vector4 div(androidx.compose.ui.graphics.vectormath.Vector4 v);
- method public operator float get(androidx.compose.ui.graphics.vectormath.VectorComponent index);
- method public operator androidx.compose.ui.graphics.vectormath.Vector2 get(androidx.compose.ui.graphics.vectormath.VectorComponent index1, androidx.compose.ui.graphics.vectormath.VectorComponent index2);
- method public operator androidx.compose.ui.graphics.vectormath.Vector3 get(androidx.compose.ui.graphics.vectormath.VectorComponent index1, androidx.compose.ui.graphics.vectormath.VectorComponent index2, androidx.compose.ui.graphics.vectormath.VectorComponent index3);
- method public operator androidx.compose.ui.graphics.vectormath.Vector4 get(androidx.compose.ui.graphics.vectormath.VectorComponent index1, androidx.compose.ui.graphics.vectormath.VectorComponent index2, androidx.compose.ui.graphics.vectormath.VectorComponent index3, androidx.compose.ui.graphics.vectormath.VectorComponent index4);
- method public operator float get(int index);
- method public operator androidx.compose.ui.graphics.vectormath.Vector2 get(int index1, int index2);
- method public operator androidx.compose.ui.graphics.vectormath.Vector3 get(int index1, int index2, int index3);
- method public operator androidx.compose.ui.graphics.vectormath.Vector4 get(int index1, int index2, int index3, int index4);
- method public inline float getA();
- method public inline float getB();
- method public inline float getG();
- method public inline float getP();
- method public inline float getQ();
- method public inline float getR();
- method public inline androidx.compose.ui.graphics.vectormath.Vector2 getRg();
- method public inline androidx.compose.ui.graphics.vectormath.Vector3 getRgb();
- method public inline androidx.compose.ui.graphics.vectormath.Vector4 getRgba();
- method public inline float getS();
- method public inline androidx.compose.ui.graphics.vectormath.Vector2 getSt();
- method public inline androidx.compose.ui.graphics.vectormath.Vector3 getStp();
- method public inline androidx.compose.ui.graphics.vectormath.Vector4 getStpq();
- method public inline float getT();
- method public inline java.util.List<java.lang.Float> getV4storage();
- method public float getW();
- method public float getX();
- method public inline androidx.compose.ui.graphics.vectormath.Vector2 getXy();
- method public inline androidx.compose.ui.graphics.vectormath.Vector3 getXyz();
- method public inline androidx.compose.ui.graphics.vectormath.Vector4 getXyzw();
- method public float getY();
- method public float getZ();
- method public operator androidx.compose.ui.graphics.vectormath.Vector4 inc();
- method public inline operator androidx.compose.ui.graphics.vectormath.Vector4 minus(float v);
- method public inline operator androidx.compose.ui.graphics.vectormath.Vector4 minus(androidx.compose.ui.graphics.vectormath.Vector2 v);
- method public inline operator androidx.compose.ui.graphics.vectormath.Vector4 minus(androidx.compose.ui.graphics.vectormath.Vector3 v);
- method public inline operator androidx.compose.ui.graphics.vectormath.Vector4 minus(androidx.compose.ui.graphics.vectormath.Vector4 v);
- method public inline operator androidx.compose.ui.graphics.vectormath.Vector4 plus(float v);
- method public inline operator androidx.compose.ui.graphics.vectormath.Vector4 plus(androidx.compose.ui.graphics.vectormath.Vector2 v);
- method public inline operator androidx.compose.ui.graphics.vectormath.Vector4 plus(androidx.compose.ui.graphics.vectormath.Vector3 v);
- method public inline operator androidx.compose.ui.graphics.vectormath.Vector4 plus(androidx.compose.ui.graphics.vectormath.Vector4 v);
- method public operator void set(int index, float v);
- method public operator void set(int index1, int index2, float v);
- method public operator void set(int index1, int index2, int index3, float v);
- method public operator void set(int index1, int index2, int index3, int index4, float v);
- method public operator void set(androidx.compose.ui.graphics.vectormath.VectorComponent index, float v);
- method public operator void set(androidx.compose.ui.graphics.vectormath.VectorComponent index1, androidx.compose.ui.graphics.vectormath.VectorComponent index2, float v);
- method public operator void set(androidx.compose.ui.graphics.vectormath.VectorComponent index1, androidx.compose.ui.graphics.vectormath.VectorComponent index2, androidx.compose.ui.graphics.vectormath.VectorComponent index3, float v);
- method public operator void set(androidx.compose.ui.graphics.vectormath.VectorComponent index1, androidx.compose.ui.graphics.vectormath.VectorComponent index2, androidx.compose.ui.graphics.vectormath.VectorComponent index3, androidx.compose.ui.graphics.vectormath.VectorComponent index4, float v);
- method public inline void setA(float value);
- method public inline void setB(float value);
- method public inline void setG(float value);
- method public inline void setP(float value);
- method public inline void setQ(float value);
- method public inline void setR(float value);
- method public inline void setRg(androidx.compose.ui.graphics.vectormath.Vector2 value);
- method public inline void setRgb(androidx.compose.ui.graphics.vectormath.Vector3 value);
- method public inline void setRgba(androidx.compose.ui.graphics.vectormath.Vector4 value);
- method public inline void setS(float value);
- method public inline void setSt(androidx.compose.ui.graphics.vectormath.Vector2 value);
- method public inline void setStp(androidx.compose.ui.graphics.vectormath.Vector3 value);
- method public inline void setStpq(androidx.compose.ui.graphics.vectormath.Vector4 value);
- method public inline void setT(float value);
- method public void setW(float p);
- method public void setX(float p);
- method public inline void setXy(androidx.compose.ui.graphics.vectormath.Vector2 value);
- method public inline void setXyz(androidx.compose.ui.graphics.vectormath.Vector3 value);
- method public inline void setXyzw(androidx.compose.ui.graphics.vectormath.Vector4 value);
- method public void setY(float p);
- method public void setZ(float p);
- method public inline operator androidx.compose.ui.graphics.vectormath.Vector4 times(float v);
- method public inline operator androidx.compose.ui.graphics.vectormath.Vector4 times(androidx.compose.ui.graphics.vectormath.Vector2 v);
- method public inline operator androidx.compose.ui.graphics.vectormath.Vector4 times(androidx.compose.ui.graphics.vectormath.Vector3 v);
- method public inline operator androidx.compose.ui.graphics.vectormath.Vector4 times(androidx.compose.ui.graphics.vectormath.Vector4 v);
- method public inline androidx.compose.ui.graphics.vectormath.Vector4 transform(kotlin.jvm.functions.Function1<? super java.lang.Float,java.lang.Float> block);
- method public operator androidx.compose.ui.graphics.vectormath.Vector4 unaryMinus();
- property public final inline float a;
- property public final inline float b;
- property public final inline float g;
- property public final inline float p;
- property public final inline float q;
- property public final inline float r;
- property public final inline androidx.compose.ui.graphics.vectormath.Vector2 rg;
- property public final inline androidx.compose.ui.graphics.vectormath.Vector3 rgb;
- property public final inline androidx.compose.ui.graphics.vectormath.Vector4 rgba;
- property public final inline float s;
- property public final inline androidx.compose.ui.graphics.vectormath.Vector2 st;
- property public final inline androidx.compose.ui.graphics.vectormath.Vector3 stp;
- property public final inline androidx.compose.ui.graphics.vectormath.Vector4 stpq;
- property public final inline float t;
- property public final inline java.util.List<java.lang.Float> v4storage;
- property public final inline androidx.compose.ui.graphics.vectormath.Vector2 xy;
- property public final inline androidx.compose.ui.graphics.vectormath.Vector3 xyz;
- property public final inline androidx.compose.ui.graphics.vectormath.Vector4 xyzw;
- }
-
- public enum VectorComponent {
- method public static androidx.compose.ui.graphics.vectormath.VectorComponent valueOf(String name) throws java.lang.IllegalArgumentException;
- method public static androidx.compose.ui.graphics.vectormath.VectorComponent[] values();
- enum_constant public static final androidx.compose.ui.graphics.vectormath.VectorComponent A;
- enum_constant public static final androidx.compose.ui.graphics.vectormath.VectorComponent B;
- enum_constant public static final androidx.compose.ui.graphics.vectormath.VectorComponent G;
- enum_constant public static final androidx.compose.ui.graphics.vectormath.VectorComponent P;
- enum_constant public static final androidx.compose.ui.graphics.vectormath.VectorComponent Q;
- enum_constant public static final androidx.compose.ui.graphics.vectormath.VectorComponent R;
- enum_constant public static final androidx.compose.ui.graphics.vectormath.VectorComponent S;
- enum_constant public static final androidx.compose.ui.graphics.vectormath.VectorComponent T;
- enum_constant public static final androidx.compose.ui.graphics.vectormath.VectorComponent W;
- enum_constant public static final androidx.compose.ui.graphics.vectormath.VectorComponent X;
- enum_constant public static final androidx.compose.ui.graphics.vectormath.VectorComponent Y;
- enum_constant public static final androidx.compose.ui.graphics.vectormath.VectorComponent Z;
- }
-
- public final class VectorExtensionsKt {
- method public static inline androidx.compose.ui.graphics.vectormath.Vector2 abs(androidx.compose.ui.graphics.vectormath.Vector2 v);
- method public static inline androidx.compose.ui.graphics.vectormath.Vector3 abs(androidx.compose.ui.graphics.vectormath.Vector3 v);
- method public static inline androidx.compose.ui.graphics.vectormath.Vector4 abs(androidx.compose.ui.graphics.vectormath.Vector4 v);
- method public static inline androidx.compose.ui.graphics.vectormath.Vector2 coerceIn(androidx.compose.ui.graphics.vectormath.Vector2, float min, float max);
- method public static inline androidx.compose.ui.graphics.vectormath.Vector2 coerceIn(androidx.compose.ui.graphics.vectormath.Vector2, androidx.compose.ui.graphics.vectormath.Vector2 min, androidx.compose.ui.graphics.vectormath.Vector2 max);
- method public static inline androidx.compose.ui.graphics.vectormath.Vector3 coerceIn(androidx.compose.ui.graphics.vectormath.Vector3, float min, float max);
- method public static inline androidx.compose.ui.graphics.vectormath.Vector3 coerceIn(androidx.compose.ui.graphics.vectormath.Vector3, androidx.compose.ui.graphics.vectormath.Vector3 min, androidx.compose.ui.graphics.vectormath.Vector3 max);
- method public static inline androidx.compose.ui.graphics.vectormath.Vector4 coerceIn(androidx.compose.ui.graphics.vectormath.Vector4, float min, float max);
- method public static inline androidx.compose.ui.graphics.vectormath.Vector4 coerceIn(androidx.compose.ui.graphics.vectormath.Vector4, androidx.compose.ui.graphics.vectormath.Vector4 min, androidx.compose.ui.graphics.vectormath.Vector4 max);
- method public static inline androidx.compose.ui.graphics.vectormath.Vector3 cross(androidx.compose.ui.graphics.vectormath.Vector3 a, androidx.compose.ui.graphics.vectormath.Vector3 b);
- method public static inline float distance(androidx.compose.ui.graphics.vectormath.Vector2 a, androidx.compose.ui.graphics.vectormath.Vector2 b);
- method public static inline float distance(androidx.compose.ui.graphics.vectormath.Vector3 a, androidx.compose.ui.graphics.vectormath.Vector3 b);
- method public static inline float distance(androidx.compose.ui.graphics.vectormath.Vector4 a, androidx.compose.ui.graphics.vectormath.Vector4 b);
- method public static inline operator androidx.compose.ui.graphics.vectormath.Vector2 div(float, androidx.compose.ui.graphics.vectormath.Vector2 v);
- method public static inline operator androidx.compose.ui.graphics.vectormath.Vector3 div(float, androidx.compose.ui.graphics.vectormath.Vector3 v);
- method public static inline operator androidx.compose.ui.graphics.vectormath.Vector4 div(float, androidx.compose.ui.graphics.vectormath.Vector4 v);
- method public static inline float dot(androidx.compose.ui.graphics.vectormath.Vector2 a, androidx.compose.ui.graphics.vectormath.Vector2 b);
- method public static inline float dot(androidx.compose.ui.graphics.vectormath.Vector3 a, androidx.compose.ui.graphics.vectormath.Vector3 b);
- method public static inline float dot(androidx.compose.ui.graphics.vectormath.Vector4 a, androidx.compose.ui.graphics.vectormath.Vector4 b);
- method public static inline float length(androidx.compose.ui.graphics.vectormath.Vector2 v);
- method public static inline float length(androidx.compose.ui.graphics.vectormath.Vector3 v);
- method public static inline float length(androidx.compose.ui.graphics.vectormath.Vector4 v);
- method public static inline float length2(androidx.compose.ui.graphics.vectormath.Vector2 v);
- method public static inline float length2(androidx.compose.ui.graphics.vectormath.Vector3 v);
- method public static inline float length2(androidx.compose.ui.graphics.vectormath.Vector4 v);
- method public static inline float max(androidx.compose.ui.graphics.vectormath.Vector2 v);
- method public static inline androidx.compose.ui.graphics.vectormath.Vector2 max(androidx.compose.ui.graphics.vectormath.Vector2 a, androidx.compose.ui.graphics.vectormath.Vector2 b);
- method public static inline float max(androidx.compose.ui.graphics.vectormath.Vector3 v);
- method public static inline androidx.compose.ui.graphics.vectormath.Vector3 max(androidx.compose.ui.graphics.vectormath.Vector3 a, androidx.compose.ui.graphics.vectormath.Vector3 b);
- method public static inline float max(androidx.compose.ui.graphics.vectormath.Vector4 v);
- method public static inline androidx.compose.ui.graphics.vectormath.Vector4 max(androidx.compose.ui.graphics.vectormath.Vector4 a, androidx.compose.ui.graphics.vectormath.Vector4 b);
- method public static inline float min(androidx.compose.ui.graphics.vectormath.Vector2 v);
- method public static inline androidx.compose.ui.graphics.vectormath.Vector2 min(androidx.compose.ui.graphics.vectormath.Vector2 a, androidx.compose.ui.graphics.vectormath.Vector2 b);
- method public static inline float min(androidx.compose.ui.graphics.vectormath.Vector3 v);
- method public static inline androidx.compose.ui.graphics.vectormath.Vector3 min(androidx.compose.ui.graphics.vectormath.Vector3 a, androidx.compose.ui.graphics.vectormath.Vector3 b);
- method public static inline float min(androidx.compose.ui.graphics.vectormath.Vector4 v);
- method public static inline androidx.compose.ui.graphics.vectormath.Vector4 min(androidx.compose.ui.graphics.vectormath.Vector4 a, androidx.compose.ui.graphics.vectormath.Vector4 b);
- method public static inline operator androidx.compose.ui.graphics.vectormath.Vector2 minus(float, androidx.compose.ui.graphics.vectormath.Vector2 v);
- method public static inline operator androidx.compose.ui.graphics.vectormath.Vector3 minus(float, androidx.compose.ui.graphics.vectormath.Vector3 v);
- method public static inline operator androidx.compose.ui.graphics.vectormath.Vector4 minus(float, androidx.compose.ui.graphics.vectormath.Vector4 v);
- method public static androidx.compose.ui.graphics.vectormath.Vector2 normalize(androidx.compose.ui.graphics.vectormath.Vector2 v);
- method public static androidx.compose.ui.graphics.vectormath.Vector3 normalize(androidx.compose.ui.graphics.vectormath.Vector3 v);
- method public static androidx.compose.ui.graphics.vectormath.Vector4 normalize(androidx.compose.ui.graphics.vectormath.Vector4 v);
- method public static inline operator androidx.compose.ui.graphics.vectormath.Vector2 plus(float, androidx.compose.ui.graphics.vectormath.Vector2 v);
- method public static inline operator androidx.compose.ui.graphics.vectormath.Vector3 plus(float, androidx.compose.ui.graphics.vectormath.Vector3 v);
- method public static inline operator androidx.compose.ui.graphics.vectormath.Vector4 plus(float, androidx.compose.ui.graphics.vectormath.Vector4 v);
- method public static inline androidx.compose.ui.graphics.vectormath.Vector2 reflect(androidx.compose.ui.graphics.vectormath.Vector2 i, androidx.compose.ui.graphics.vectormath.Vector2 n);
- method public static inline androidx.compose.ui.graphics.vectormath.Vector3 reflect(androidx.compose.ui.graphics.vectormath.Vector3 i, androidx.compose.ui.graphics.vectormath.Vector3 n);
- method public static androidx.compose.ui.graphics.vectormath.Vector2 refract(androidx.compose.ui.graphics.vectormath.Vector2 i, androidx.compose.ui.graphics.vectormath.Vector2 n, float eta);
- method public static androidx.compose.ui.graphics.vectormath.Vector3 refract(androidx.compose.ui.graphics.vectormath.Vector3 i, androidx.compose.ui.graphics.vectormath.Vector3 n, float eta);
- method public static inline operator androidx.compose.ui.graphics.vectormath.Vector2 times(float, androidx.compose.ui.graphics.vectormath.Vector2 v);
- method public static inline operator androidx.compose.ui.graphics.vectormath.Vector3 times(float, androidx.compose.ui.graphics.vectormath.Vector3 v);
- method public static inline operator androidx.compose.ui.graphics.vectormath.Vector4 times(float, androidx.compose.ui.graphics.vectormath.Vector4 v);
- method public static inline androidx.compose.ui.graphics.vectormath.Vector2 transform(androidx.compose.ui.graphics.vectormath.Vector2 v, kotlin.jvm.functions.Function1<? super java.lang.Float,java.lang.Float> block);
- method public static inline androidx.compose.ui.graphics.vectormath.Vector3 transform(androidx.compose.ui.graphics.vectormath.Vector3 v, kotlin.jvm.functions.Function1<? super java.lang.Float,java.lang.Float> block);
- method public static inline androidx.compose.ui.graphics.vectormath.Vector4 transform(androidx.compose.ui.graphics.vectormath.Vector4 v, kotlin.jvm.functions.Function1<? super java.lang.Float,java.lang.Float> block);
- method public static inline infix androidx.compose.ui.graphics.vectormath.Vector3 x(androidx.compose.ui.graphics.vectormath.Vector3, androidx.compose.ui.graphics.vectormath.Vector3 v);
- }
-
-}
-
diff --git a/compose/ui/ui-graphics/build.gradle b/compose/ui/ui-graphics/build.gradle
index 42ed409..39c531b 100644
--- a/compose/ui/ui-graphics/build.gradle
+++ b/compose/ui/ui-graphics/build.gradle
@@ -71,7 +71,7 @@
androidx {
name = "Compose Graphics"
publish = Publish.SNAPSHOT_AND_RELEASE
- mavenVersion = LibraryVersions.UI
+ mavenVersion = LibraryVersions.COMPOSE
mavenGroup = LibraryGroups.Compose.UI
inceptionYear = "2020"
description = "Compose graphics"
diff --git a/compose/ui/ui-graphics/src/androidAndroidTest/kotlin/androidx/compose/ui/graphics/PathTest.kt b/compose/ui/ui-graphics/src/androidAndroidTest/kotlin/androidx/compose/ui/graphics/PathTest.kt
index 38f5099..74372ca 100644
--- a/compose/ui/ui-graphics/src/androidAndroidTest/kotlin/androidx/compose/ui/graphics/PathTest.kt
+++ b/compose/ui/ui-graphics/src/androidAndroidTest/kotlin/androidx/compose/ui/graphics/PathTest.kt
@@ -19,13 +19,12 @@
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.geometry.Rect
import androidx.compose.ui.geometry.Size
-import androidx.compose.ui.graphics.vectormath.PI
-import androidx.compose.ui.graphics.vectormath.radians
import androidx.test.filters.SmallTest
import org.junit.Assert.assertEquals
import org.junit.Test
import org.junit.runner.RunWith
import org.junit.runners.JUnit4
+import kotlin.math.PI
@SmallTest
@RunWith(JUnit4::class)
@@ -41,7 +40,7 @@
addArcRad(
Rect(Offset.Zero, Size(width.toFloat(), height.toFloat())),
0.0f,
- PI / 2
+ PI.toFloat() / 2
)
}
@@ -52,8 +51,8 @@
val path2 = Path().apply {
arcToRad(
Rect(Offset(0.0f, 0.0f), Size(width.toFloat(), height.toFloat())),
- PI,
- PI / 2,
+ PI.toFloat(),
+ PI.toFloat() / 2,
false
)
close()
@@ -62,7 +61,7 @@
canvas.drawPath(path2, arcPaint)
val pixelmap = image.toPixelMap()
- val x = (50.0 * Math.cos(radians(45.0f).toDouble())).toInt()
+ val x = (50.0 * Math.cos(PI / 4)).toInt()
assertEquals(arcColor,
pixelmap[
width / 2 + x - 1,
diff --git a/compose/ui/ui-graphics/src/androidMain/kotlin/androidx/compose/ui/graphics/AndroidCanvas.kt b/compose/ui/ui-graphics/src/androidMain/kotlin/androidx/compose/ui/graphics/AndroidCanvas.kt
index 2155210..cbf85b8 100644
--- a/compose/ui/ui-graphics/src/androidMain/kotlin/androidx/compose/ui/graphics/AndroidCanvas.kt
+++ b/compose/ui/ui-graphics/src/androidMain/kotlin/androidx/compose/ui/graphics/AndroidCanvas.kt
@@ -16,11 +16,8 @@
package androidx.compose.ui.graphics
-import android.graphics.Matrix
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.geometry.Rect
-import androidx.compose.ui.graphics.vectormath.Matrix4
-import androidx.compose.ui.graphics.vectormath.isIdentity
import androidx.compose.ui.unit.IntOffset
import androidx.compose.ui.unit.IntSize
import androidx.compose.ui.util.fastForEach
@@ -139,30 +136,10 @@
/**
* @throws IllegalStateException if an arbitrary transform is provided
*/
- override fun concat(matrix4: Matrix4) {
- if (!matrix4.isIdentity()) {
- val frameworkMatrix = Matrix()
- if (matrix4.get(2, 0) != 0f ||
- matrix4.get(2, 1) != 0f ||
- matrix4.get(2, 0) != 0f ||
- matrix4.get(2, 1) != 0f ||
- matrix4.get(2, 2) != 1f ||
- matrix4.get(2, 3) != 0f ||
- matrix4.get(3, 2) != 0f) {
- throw IllegalStateException("Android does not support arbitrary transforms")
- }
- val values = floatArrayOf(
- matrix4.get(0, 0),
- matrix4.get(1, 0),
- matrix4.get(3, 0),
- matrix4.get(0, 1),
- matrix4.get(1, 1),
- matrix4.get(3, 1),
- matrix4.get(0, 3),
- matrix4.get(1, 3),
- matrix4.get(3, 3)
- )
- frameworkMatrix.setValues(values)
+ override fun concat(matrix: Matrix) {
+ if (!matrix.isIdentity()) {
+ val frameworkMatrix = android.graphics.Matrix()
+ frameworkMatrix.setFrom(matrix)
internalCanvas.concat(frameworkMatrix)
}
}
diff --git a/compose/ui/ui-graphics/src/androidMain/kotlin/androidx/compose/ui/graphics/AndroidMatrixConversions.kt b/compose/ui/ui-graphics/src/androidMain/kotlin/androidx/compose/ui/graphics/AndroidMatrixConversions.kt
new file mode 100644
index 0000000..d5b9fe3
--- /dev/null
+++ b/compose/ui/ui-graphics/src/androidMain/kotlin/androidx/compose/ui/graphics/AndroidMatrixConversions.kt
@@ -0,0 +1,94 @@
+/*
+ * Copyright 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.compose.ui.graphics
+
+/**
+ * Set the matrix values the native [android.graphics.Matrix].
+ */
+fun Matrix.setFrom(matrix: android.graphics.Matrix) {
+ val v = values
+ matrix.getValues(v)
+
+ val v30 = v[2]
+ val v01 = v[3]
+ val v11 = v[4]
+ val v31 = v[5]
+ val v03 = v[6]
+ val v13 = v[7]
+ val v33 = v[8]
+
+ // this[0, 0] and this[1, 0] are already set properly
+ this[2, 0] = 0f
+ this[3, 0] = v30
+ this[0, 1] = v01
+ this[1, 1] = v11
+ this[2, 1] = 0f
+ this[3, 1] = v31
+ this[0, 2] = 0f
+ this[1, 2] = 0f
+ this[2, 2] = 1f
+ this[3, 2] = 0f
+ this[0, 3] = v03
+ this[1, 3] = v13
+ this[2, 3] = 0f
+ this[3, 3] = v33
+}
+
+/**
+ * Set the native [android.graphics.Matrix] from [matrix].
+ */
+fun android.graphics.Matrix.setFrom(matrix: Matrix) {
+ require(
+ matrix[0, 2] == 0f &&
+ matrix[1, 2] == 0f &&
+ matrix[2, 2] == 1f &&
+ matrix[3, 2] == 0f &&
+ matrix[2, 0] == 0f &&
+ matrix[2, 1] == 0f &&
+ matrix[2, 3] == 0f
+ ) {
+ "Android does not support arbitrary transforms"
+ }
+ val v01 = matrix[0, 1]
+ val v03 = matrix[0, 3]
+ val v11 = matrix[1, 1]
+ // val v13 = matrix[1, 3]
+ val v30 = matrix[3, 0]
+ val v31 = matrix[3, 1]
+ val v33 = matrix[3, 3]
+
+ // We'll reuse the array used in Matrix to avoid allocation by temporarily
+ // setting it to the 3x3 matrix used by android.graphics.Matrix
+ val v = matrix.values
+ v[2] = v30
+ v[3] = v01
+ v[4] = v11
+ v[5] = v31
+ v[6] = v03
+ // v[7] = v13 // math works out so we don't have to set it
+ v[8] = v33
+ setValues(v)
+
+ // now reset the values we just set temporarily
+ v[2] = 0f
+ v[3] = v30
+ v[4] = v01
+ v[5] = v11
+ v[6] = 0f
+ // v[7] = v13
+ v[8] = 0f
+}
diff --git a/compose/ui/ui-graphics/src/androidMain/kotlin/androidx/compose/ui/graphics/AndroidPath.kt b/compose/ui/ui-graphics/src/androidMain/kotlin/androidx/compose/ui/graphics/AndroidPath.kt
index d3c26be..b61bae3 100644
--- a/compose/ui/ui-graphics/src/androidMain/kotlin/androidx/compose/ui/graphics/AndroidPath.kt
+++ b/compose/ui/ui-graphics/src/androidMain/kotlin/androidx/compose/ui/graphics/AndroidPath.kt
@@ -19,7 +19,6 @@
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.geometry.Rect
import androidx.compose.ui.geometry.RoundRect
-import androidx.compose.ui.graphics.vectormath.degrees
actual fun Path(): Path = AndroidPath()
diff --git a/compose/ui/ui-graphics/src/commonMain/kotlin/androidx/compose/ui/graphics/Canvas.kt b/compose/ui/ui-graphics/src/commonMain/kotlin/androidx/compose/ui/graphics/Canvas.kt
index b80e667..9df70da 100644
--- a/compose/ui/ui-graphics/src/commonMain/kotlin/androidx/compose/ui/graphics/Canvas.kt
+++ b/compose/ui/ui-graphics/src/commonMain/kotlin/androidx/compose/ui/graphics/Canvas.kt
@@ -18,8 +18,6 @@
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.geometry.Rect
-import androidx.compose.ui.graphics.vectormath.Matrix4
-import androidx.compose.ui.graphics.vectormath.degrees
import androidx.compose.ui.unit.IntOffset
import androidx.compose.ui.unit.IntSize
@@ -304,7 +302,7 @@
* Multiply the current transform by the specified 4⨉4 transformation matrix
* specified as a list of values in column-major order.
*/
- fun concat(matrix4: Matrix4)
+ fun concat(matrix: Matrix)
/**
* Reduces the clip region to the intersection of the current clip and the
diff --git a/appsearch/appsearch/src/main/java/androidx/appsearch/impl/package-info.java b/compose/ui/ui-graphics/src/commonMain/kotlin/androidx/compose/ui/graphics/Degrees.kt
similarity index 71%
copy from appsearch/appsearch/src/main/java/androidx/appsearch/impl/package-info.java
copy to compose/ui/ui-graphics/src/commonMain/kotlin/androidx/compose/ui/graphics/Degrees.kt
index 68c3b98..bf2f08d 100644
--- a/appsearch/appsearch/src/main/java/androidx/appsearch/impl/package-info.java
+++ b/compose/ui/ui-graphics/src/commonMain/kotlin/androidx/compose/ui/graphics/Degrees.kt
@@ -14,10 +14,14 @@
* limitations under the License.
*/
-/**
- * @hide
- */
-@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
-package androidx.appsearch.impl;
+package androidx.compose.ui.graphics
-import androidx.annotation.RestrictTo;
+import kotlin.math.PI
+
+private const val RadiansToDegrees = (180.0 / PI).toFloat()
+
+/**
+ * Converts [radians] to degrees.
+ */
+@PublishedApi
+internal fun degrees(radians: Float): Float = RadiansToDegrees * radians
\ No newline at end of file
diff --git a/compose/ui/ui-graphics/src/commonMain/kotlin/androidx/compose/ui/graphics/Matrix.kt b/compose/ui/ui-graphics/src/commonMain/kotlin/androidx/compose/ui/graphics/Matrix.kt
new file mode 100644
index 0000000..82180cb
--- /dev/null
+++ b/compose/ui/ui-graphics/src/commonMain/kotlin/androidx/compose/ui/graphics/Matrix.kt
@@ -0,0 +1,379 @@
+/*
+ * Copyright 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+@file:Suppress("NOTHING_TO_INLINE")
+
+package androidx.compose.ui.graphics
+
+import androidx.compose.ui.geometry.MutableRect
+import androidx.compose.ui.geometry.Offset
+import androidx.compose.ui.geometry.Rect
+import kotlin.math.PI
+import kotlin.math.cos
+import kotlin.math.max
+import kotlin.math.min
+import kotlin.math.sin
+
+// TODO(mount): This class needs some optimization
+@Suppress("EXPERIMENTAL_FEATURE_WARNING")
+inline class Matrix(
+ val values: FloatArray = floatArrayOf(
+ 1f, 0f, 0f, 0f,
+ 0f, 1f, 0f, 0f,
+ 0f, 0f, 1f, 0f,
+ 0f, 0f, 0f, 1f
+ )
+) {
+ inline operator fun get(row: Int, column: Int) = values[row + (column * 4)]
+
+ inline operator fun set(row: Int, column: Int, v: Float) {
+ values[row + (column * 4)] = v
+ }
+
+ /**
+ * Does the 3D transform on [point] and returns the `x` and `y` values in an [Offset].
+ */
+ fun map(point: Offset): Offset {
+ val x = point.x
+ val y = point.y
+ return Offset(
+ x = this[0, 0] * x + this[1, 0] * y + this[3, 0],
+ y = this[0, 1] * x + this[1, 1] * y + this[3, 1]
+ )
+ }
+
+ /**
+ * Does a 3D transform on [rect] and returns its bounds after the transform.
+ */
+ fun map(rect: Rect): Rect {
+ val p0 = map(Offset(rect.left, rect.top))
+ val p1 = map(Offset(rect.left, rect.bottom))
+ val p3 = map(Offset(rect.right, rect.top))
+ val p4 = map(Offset(rect.right, rect.bottom))
+
+ val left = min(min(p0.x, p1.x), min(p3.x, p4.x))
+ val top = min(min(p0.y, p1.y), min(p3.y, p4.y))
+ val right = max(max(p0.x, p1.x), max(p3.x, p4.x))
+ val bottom = max(max(p0.y, p1.y), max(p3.y, p4.y))
+ return Rect(left, top, right, bottom)
+ }
+
+ /**
+ * Does a 3D transform on [rect], transforming [rect] with the results.
+ */
+ fun map(rect: MutableRect) {
+ val p0 = map(Offset(rect.left, rect.top))
+ val p1 = map(Offset(rect.left, rect.bottom))
+ val p3 = map(Offset(rect.right, rect.top))
+ val p4 = map(Offset(rect.right, rect.bottom))
+
+ rect.left = min(min(p0.x, p1.x), min(p3.x, p4.x))
+ rect.top = min(min(p0.y, p1.y), min(p3.y, p4.y))
+ rect.right = max(max(p0.x, p1.x), max(p3.x, p4.x))
+ rect.bottom = max(max(p0.y, p1.y), max(p3.y, p4.y))
+ }
+
+ /**
+ * Multiply this matrix by [m] and assign the result to this matrix.
+ */
+ operator fun timesAssign(m: Matrix) {
+ val v00 = dot(this, 0, m, 0)
+ val v01 = dot(this, 0, m, 1)
+ val v02 = dot(this, 0, m, 2)
+ val v03 = dot(this, 0, m, 3)
+ val v10 = dot(this, 1, m, 0)
+ val v11 = dot(this, 1, m, 1)
+ val v12 = dot(this, 1, m, 2)
+ val v13 = dot(this, 1, m, 3)
+ val v20 = dot(this, 2, m, 0)
+ val v21 = dot(this, 2, m, 1)
+ val v22 = dot(this, 2, m, 2)
+ val v23 = dot(this, 2, m, 3)
+ val v30 = dot(this, 3, m, 0)
+ val v31 = dot(this, 3, m, 1)
+ val v32 = dot(this, 3, m, 2)
+ val v33 = dot(this, 3, m, 3)
+ this[0, 0] = v00
+ this[0, 1] = v01
+ this[0, 2] = v02
+ this[0, 3] = v03
+ this[1, 0] = v10
+ this[1, 1] = v11
+ this[1, 2] = v12
+ this[1, 3] = v13
+ this[2, 0] = v20
+ this[2, 1] = v21
+ this[2, 2] = v22
+ this[2, 3] = v23
+ this[3, 0] = v30
+ this[3, 1] = v31
+ this[3, 2] = v32
+ this[3, 3] = v33
+ }
+
+ override fun toString(): String {
+ return """
+ |${this[0, 0]} ${this[0, 1]} ${this[0, 2]} ${this[0, 3]}|
+ |${this[1, 0]} ${this[1, 1]} ${this[1, 2]} ${this[1, 3]}|
+ |${this[2, 0]} ${this[2, 1]} ${this[2, 2]} ${this[2, 3]}|
+ |${this[3, 0]} ${this[3, 1]} ${this[3, 2]} ${this[3, 3]}|
+ """.trimIndent()
+ }
+
+ /**
+ * Invert `this` Matrix.
+ */
+ fun invert() {
+ val a00 = this[0, 0]
+ val a01 = this[0, 1]
+ val a02 = this[0, 2]
+ val a03 = this[0, 3]
+ val a10 = this[1, 0]
+ val a11 = this[1, 1]
+ val a12 = this[1, 2]
+ val a13 = this[1, 3]
+ val a20 = this[2, 0]
+ val a21 = this[2, 1]
+ val a22 = this[2, 2]
+ val a23 = this[2, 3]
+ val a30 = this[3, 0]
+ val a31 = this[3, 1]
+ val a32 = this[3, 2]
+ val a33 = this[3, 3]
+ val b00 = a00 * a11 - a01 * a10
+ val b01 = a00 * a12 - a02 * a10
+ val b02 = a00 * a13 - a03 * a10
+ val b03 = a01 * a12 - a02 * a11
+ val b04 = a01 * a13 - a03 * a11
+ val b05 = a02 * a13 - a03 * a12
+ val b06 = a20 * a31 - a21 * a30
+ val b07 = a20 * a32 - a22 * a30
+ val b08 = a20 * a33 - a23 * a30
+ val b09 = a21 * a32 - a22 * a31
+ val b10 = a21 * a33 - a23 * a31
+ val b11 = a22 * a33 - a23 * a32
+ val det =
+ (b00 * b11 - b01 * b10 + b02 * b09 + b03 * b08 - b04 * b07 + b05 * b06)
+ if (det == 0.0f) {
+ return
+ }
+ val invDet = 1.0f / det
+ this[0, 0] = ((a11 * b11 - a12 * b10 + a13 * b09) * invDet)
+ this[0, 1] = ((-a01 * b11 + a02 * b10 - a03 * b09) * invDet)
+ this[0, 2] = ((a31 * b05 - a32 * b04 + a33 * b03) * invDet)
+ this[0, 3] = ((-a21 * b05 + a22 * b04 - a23 * b03) * invDet)
+ this[1, 0] = ((-a10 * b11 + a12 * b08 - a13 * b07) * invDet)
+ this[1, 1] = ((a00 * b11 - a02 * b08 + a03 * b07) * invDet)
+ this[1, 2] = ((-a30 * b05 + a32 * b02 - a33 * b01) * invDet)
+ this[1, 3] = ((a20 * b05 - a22 * b02 + a23 * b01) * invDet)
+ this[2, 0] = ((a10 * b10 - a11 * b08 + a13 * b06) * invDet)
+ this[2, 1] = ((-a00 * b10 + a01 * b08 - a03 * b06) * invDet)
+ this[2, 2] = ((a30 * b04 - a31 * b02 + a33 * b00) * invDet)
+ this[2, 3] = ((-a20 * b04 + a21 * b02 - a23 * b00) * invDet)
+ this[3, 0] = ((-a10 * b09 + a11 * b07 - a12 * b06) * invDet)
+ this[3, 1] = ((a00 * b09 - a01 * b07 + a02 * b06) * invDet)
+ this[3, 2] = ((-a30 * b03 + a31 * b01 - a32 * b00) * invDet)
+ this[3, 3] = ((a20 * b03 - a21 * b01 + a22 * b00) * invDet)
+ }
+
+ /**
+ * Resets the `this` to the identity matrix.
+ */
+ fun reset() {
+ for (c in 0..3) {
+ for (r in 0..3) {
+ this.set(r, c, if (c == r) 1f else 0f)
+ }
+ }
+ }
+
+ /** Sets the entire matrix to the matrix in [matrix]. */
+ fun setFrom(matrix: Matrix) {
+ for (i in 0..15) {
+ values[i] = matrix.values[i]
+ }
+ }
+
+ /**
+ * Applies a [degrees] rotation around X to `this`.
+ */
+ fun rotateX(degrees: Float) {
+ val c = cos(degrees * PI / 180.0).toFloat()
+ val s = sin(degrees * PI / 180.0).toFloat()
+
+ val a01 = this[0, 1]
+ val a02 = this[0, 2]
+ val v01 = a01 * c - a02 * s
+ val v02 = a01 * s + a02 * c
+
+ val a11 = this[1, 1]
+ val a12 = this[1, 2]
+ val v11 = a11 * c - a12 * s
+ val v12 = a11 * s + a12 * c
+
+ val a21 = this[2, 1]
+ val a22 = this[2, 2]
+ val v21 = a21 * c - a22 * s
+ val v22 = a21 * s + a22 * c
+
+ val a31 = this[3, 1]
+ val a32 = this[3, 2]
+ val v31 = a31 * c - a32 * s
+ val v32 = a31 * s + a32 * c
+
+ this[0, 1] = v01
+ this[0, 2] = v02
+ this[1, 1] = v11
+ this[1, 2] = v12
+ this[2, 1] = v21
+ this[2, 2] = v22
+ this[3, 1] = v31
+ this[3, 2] = v32
+ }
+
+ /**
+ * Applies a [degrees] rotation around Y to `this`.
+ */
+ fun rotateY(degrees: Float) {
+ val c = cos(degrees * PI / 180.0).toFloat()
+ val s = sin(degrees * PI / 180.0).toFloat()
+
+ val a00 = this[0, 0]
+ val a02 = this[0, 2]
+ val v00 = a00 * c + a02 * s
+ val v02 = -a00 * s + a02 * c
+
+ val a10 = this[1, 0]
+ val a12 = this[1, 2]
+ val v10 = a10 * c + a12 * s
+ val v12 = -a10 * s + a12 * c
+
+ val a20 = this[2, 0]
+ val a22 = this[2, 2]
+ val v20 = a20 * c + a22 * s
+ val v22 = -a20 * s + a22 * c
+
+ val a30 = this[3, 0]
+ val a32 = this[3, 2]
+ val v30 = a30 * c + a32 * s
+ val v32 = -a30 * s + a32 * c
+
+ this[0, 0] = v00
+ this[0, 2] = v02
+ this[1, 0] = v10
+ this[1, 2] = v12
+ this[2, 0] = v20
+ this[2, 2] = v22
+ this[3, 0] = v30
+ this[3, 2] = v32
+ }
+
+ /**
+ * Applies a [degrees] rotation around Z to `this`.
+ */
+ fun rotateZ(degrees: Float) {
+ val c = cos(degrees * PI / 180.0).toFloat()
+ val s = sin(degrees * PI / 180.0).toFloat()
+
+ val a00 = this[0, 0]
+ val a10 = this[1, 0]
+ val v00 = c * a00 + s * a10
+ val v10 = -s * a00 + c * a10
+
+ val a01 = this[0, 1]
+ val a11 = this[1, 1]
+ val v01 = c * a01 + s * a11
+ val v11 = -s * a01 + c * a11
+
+ val a02 = this[0, 2]
+ val a12 = this[1, 2]
+ val v02 = c * a02 + s * a12
+ val v12 = -s * a02 + c * a12
+
+ val a03 = this[0, 3]
+ val a13 = this[1, 3]
+ val v03 = c * a03 + s * a13
+ val v13 = -s * a03 + c * a13
+
+ this[0, 0] = v00
+ this[0, 1] = v01
+ this[0, 2] = v02
+ this[0, 3] = v03
+ this[1, 0] = v10
+ this[1, 1] = v11
+ this[1, 2] = v12
+ this[1, 3] = v13
+ }
+
+ /** Scale this matrix by [x], [y], [z] */
+ fun scale(x: Float = 1f, y: Float = 1f, z: Float = 1f) {
+ this[0, 0] *= x
+ this[0, 1] *= x
+ this[0, 2] *= x
+ this[0, 3] *= x
+ this[1, 0] *= y
+ this[1, 1] *= y
+ this[1, 2] *= y
+ this[1, 3] *= y
+ this[2, 0] *= z
+ this[2, 1] *= z
+ this[2, 2] *= z
+ this[2, 3] *= z
+ }
+
+ /** Translate this matrix by [x], [y], [z] */
+ fun translate(x: Float = 0f, y: Float = 0f, z: Float = 0f) {
+ val t1 = this[0, 0] * x +
+ this[1, 0] * y +
+ this[2, 0] * z +
+ this[3, 0]
+ val t2 = this[0, 1] * x +
+ this[1, 1] * y +
+ this[2, 1] * z +
+ this[3, 1]
+ val t3 = this[0, 2] * x +
+ this[1, 2] * y +
+ this[2, 2] * z +
+ this[3, 2]
+ val t4 = this[0, 3] * x +
+ this[1, 3] * y +
+ this[2, 3] * z +
+ this[3, 3]
+ this[3, 0] = t1
+ this[3, 1] = t2
+ this[3, 2] = t3
+ this[3, 3] = t4
+ }
+}
+
+private fun dot(m1: Matrix, row: Int, m2: Matrix, column: Int): Float {
+ return m1[row, 0] * m2[0, column] +
+ m1[row, 1] * m2[1, column] +
+ m1[row, 2] * m2[2, column] +
+ m1[row, 3] * m2[3, column]
+}
+
+/** Whether the given matrix is the identity matrix. */
+fun Matrix.isIdentity(): Boolean {
+ for (row in 0..3) {
+ for (column in 0..3) {
+ val expected = if (row == column) 1f else 0f
+ if (this[row, column] != expected) {
+ return false
+ }
+ }
+ }
+ return true
+}
\ No newline at end of file
diff --git a/compose/ui/ui-graphics/src/commonMain/kotlin/androidx/compose/ui/graphics/Path.kt b/compose/ui/ui-graphics/src/commonMain/kotlin/androidx/compose/ui/graphics/Path.kt
index c9223ce..65d1df4 100644
--- a/compose/ui/ui-graphics/src/commonMain/kotlin/androidx/compose/ui/graphics/Path.kt
+++ b/compose/ui/ui-graphics/src/commonMain/kotlin/androidx/compose/ui/graphics/Path.kt
@@ -19,7 +19,6 @@
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.geometry.Rect
import androidx.compose.ui.geometry.RoundRect
-import androidx.compose.ui.graphics.vectormath.degrees
expect fun Path(): Path
@@ -283,4 +282,4 @@
"path; in particular, check for NaN values.")
}
}
-}
\ No newline at end of file
+}
diff --git a/compose/ui/ui-graphics/src/commonMain/kotlin/androidx/compose/ui/graphics/drawscope/DrawScope.kt b/compose/ui/ui-graphics/src/commonMain/kotlin/androidx/compose/ui/graphics/drawscope/DrawScope.kt
index 1f610b9..6ec5178 100644
--- a/compose/ui/ui-graphics/src/commonMain/kotlin/androidx/compose/ui/graphics/drawscope/DrawScope.kt
+++ b/compose/ui/ui-graphics/src/commonMain/kotlin/androidx/compose/ui/graphics/drawscope/DrawScope.kt
@@ -33,8 +33,8 @@
import androidx.compose.ui.graphics.PointMode
import androidx.compose.ui.graphics.StrokeCap
import androidx.compose.ui.graphics.StrokeJoin
-import androidx.compose.ui.graphics.vectormath.Matrix4
-import androidx.compose.ui.graphics.vectormath.degrees
+import androidx.compose.ui.graphics.Matrix
+import androidx.compose.ui.graphics.degrees
import androidx.compose.ui.unit.Density
import androidx.compose.ui.unit.IntOffset
import androidx.compose.ui.unit.IntSize
@@ -136,7 +136,9 @@
pivotX: Float = center.x,
pivotY: Float = center.y,
block: DrawScope.() -> Unit
-) = withTransform({ rotate(degrees(radians), pivotX, pivotY) }, block)
+) {
+ withTransform({ rotate(degrees(radians), pivotX, pivotY) }, block)
+}
/**
* Add an axis-aligned scale to the current transform, scaling by the first
@@ -307,7 +309,7 @@
}
}
- override fun transform(matrix: Matrix4) {
+ override fun transform(matrix: Matrix) {
this@DrawScope.canvas.concat(matrix)
}
}
diff --git a/compose/ui/ui-graphics/src/commonMain/kotlin/androidx/compose/ui/graphics/drawscope/DrawTransform.kt b/compose/ui/ui-graphics/src/commonMain/kotlin/androidx/compose/ui/graphics/drawscope/DrawTransform.kt
index b648aca..fc52620 100644
--- a/compose/ui/ui-graphics/src/commonMain/kotlin/androidx/compose/ui/graphics/drawscope/DrawTransform.kt
+++ b/compose/ui/ui-graphics/src/commonMain/kotlin/androidx/compose/ui/graphics/drawscope/DrawTransform.kt
@@ -20,8 +20,8 @@
import androidx.compose.ui.geometry.Size
import androidx.compose.ui.graphics.ClipOp
import androidx.compose.ui.graphics.Path
-import androidx.compose.ui.graphics.vectormath.Matrix4
-import androidx.compose.ui.graphics.vectormath.degrees
+import androidx.compose.ui.graphics.Matrix
+import androidx.compose.ui.graphics.degrees
/**
* Convenience method modifies the [DrawTransform] bounds to inset both left and right bounds by
@@ -161,5 +161,5 @@
* Transform the drawing environment by the given matrix
* @param matrix transformation matrix used to transform the drawing environment
*/
- fun transform(matrix: Matrix4)
+ fun transform(matrix: Matrix)
}
\ No newline at end of file
diff --git a/compose/ui/ui-graphics/src/commonMain/kotlin/androidx/compose/ui/graphics/drawscope/EmptyCanvas.kt b/compose/ui/ui-graphics/src/commonMain/kotlin/androidx/compose/ui/graphics/drawscope/EmptyCanvas.kt
index 364f7da..fa7014a 100644
--- a/compose/ui/ui-graphics/src/commonMain/kotlin/androidx/compose/ui/graphics/drawscope/EmptyCanvas.kt
+++ b/compose/ui/ui-graphics/src/commonMain/kotlin/androidx/compose/ui/graphics/drawscope/EmptyCanvas.kt
@@ -26,7 +26,7 @@
import androidx.compose.ui.graphics.Path
import androidx.compose.ui.graphics.PointMode
import androidx.compose.ui.graphics.Vertices
-import androidx.compose.ui.graphics.vectormath.Matrix4
+import androidx.compose.ui.graphics.Matrix
import androidx.compose.ui.unit.IntOffset
import androidx.compose.ui.unit.IntSize
@@ -69,7 +69,7 @@
throw UnsupportedOperationException()
}
- override fun concat(matrix4: Matrix4) {
+ override fun concat(matrix: Matrix) {
throw UnsupportedOperationException()
}
diff --git a/compose/ui/ui-graphics/src/commonMain/kotlin/androidx/compose/ui/graphics/vectormath/Matrix3.kt b/compose/ui/ui-graphics/src/commonMain/kotlin/androidx/compose/ui/graphics/vectormath/Matrix3.kt
deleted file mode 100644
index f6da80c..0000000
--- a/compose/ui/ui-graphics/src/commonMain/kotlin/androidx/compose/ui/graphics/vectormath/Matrix3.kt
+++ /dev/null
@@ -1,108 +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.compose.ui.graphics.vectormath
-
-data class Matrix3(
- var x: Vector3 = Vector3(x = 1.0f),
- var y: Vector3 = Vector3(y = 1.0f),
- var z: Vector3 = Vector3(z = 1.0f)
-) {
- constructor(m: Matrix3) : this(m.x.copy(), m.y.copy(), m.z.copy())
-
- companion object {
- fun of(vararg a: Float): Matrix3 {
- require(a.size >= 9)
- return Matrix3(
- Vector3(a[0], a[3], a[6]),
- Vector3(a[1], a[4], a[7]),
- Vector3(a[2], a[5], a[8])
- )
- }
-
- fun identity() = Matrix3()
- }
-
- inline val m3storage: List<Float>
- get() = x.v3storage + y.v3storage + z.v3storage
-
- operator fun get(column: Int) = when (column) {
- 0 -> x
- 1 -> y
- 2 -> z
- else -> throw IllegalArgumentException("column must be in 0..2")
- }
- operator fun get(column: Int, row: Int) = get(column)[row]
-
- operator fun get(column: MatrixColumn) = when (column) {
- MatrixColumn.X -> x
- MatrixColumn.Y -> y
- MatrixColumn.Z -> z
- else -> throw IllegalArgumentException("column must be X, Y or Z")
- }
- operator fun get(column: MatrixColumn, row: Int) = get(column)[row]
-
- operator fun set(column: Int, v: Vector3) {
- this[column].xyz = v
- }
- operator fun set(column: Int, row: Int, v: Float) {
- this[column][row] = v
- }
-
- operator fun unaryMinus() = Matrix3(-x, -y, -z)
- operator fun inc() = Matrix3(this).apply {
- ++x
- ++y
- ++z
- }
- operator fun dec() = Matrix3(this).apply {
- --x
- --y
- --z
- }
-
- operator fun plus(v: Float) = Matrix3(x + v, y + v, z + v)
- operator fun minus(v: Float) = Matrix3(x - v, y - v, z - v)
- operator fun times(v: Float) = Matrix3(x * v, y * v, z * v)
- operator fun div(v: Float) = Matrix3(x / v, y / v, z / v)
-
- operator fun times(m: Matrix3): Matrix3 {
- val t = transpose(this)
- return Matrix3(
- Vector3(dot(t.x, m.x), dot(t.y, m.x), dot(t.z, m.x)),
- Vector3(dot(t.x, m.y), dot(t.y, m.y), dot(t.z, m.y)),
- Vector3(dot(t.x, m.z), dot(t.y, m.z), dot(t.z, m.z))
- )
- }
-
- operator fun times(v: Vector3): Vector3 {
- val t = transpose(this)
- return Vector3(dot(t.x, v), dot(t.y, v), dot(t.z, v))
- }
-
- fun toFloatArray() = floatArrayOf(
- x.x, y.x, z.x,
- x.y, y.y, z.y,
- x.z, y.z, z.z
- )
-
- override fun toString(): String {
- return """
- |${x.x} ${y.x} ${z.x}|
- |${x.y} ${y.y} ${z.y}|
- |${x.z} ${y.z} ${z.z}|
- """.trimIndent()
- }
-}
diff --git a/compose/ui/ui-graphics/src/commonMain/kotlin/androidx/compose/ui/graphics/vectormath/Matrix4.kt b/compose/ui/ui-graphics/src/commonMain/kotlin/androidx/compose/ui/graphics/vectormath/Matrix4.kt
deleted file mode 100644
index 671053b..0000000
--- a/compose/ui/ui-graphics/src/commonMain/kotlin/androidx/compose/ui/graphics/vectormath/Matrix4.kt
+++ /dev/null
@@ -1,663 +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.
- */
-@file:Suppress("NOTHING_TO_INLINE")
-
-package androidx.compose.ui.graphics.vectormath
-
-import androidx.compose.ui.geometry.Offset
-import androidx.compose.ui.geometry.Rect
-import kotlin.math.asin
-import kotlin.math.atan2
-import kotlin.math.cos
-import kotlin.math.sin
-
-// TODO(mount): This class needs some optimization
-data class Matrix4(
- var x: Vector4 = Vector4(x = 1.0f),
- var y: Vector4 = Vector4(y = 1.0f),
- var z: Vector4 = Vector4(z = 1.0f),
- var w: Vector4 = Vector4(w = 1.0f)
-) {
- constructor(right: Vector3, up: Vector3, forward: Vector3, position: Vector3 = Vector3()) :
- this(Vector4(right), Vector4(up), Vector4(forward), Vector4(position, 1.0f))
-
- constructor(m: Matrix4) : this(m.x.copy(), m.y.copy(), m.z.copy(), m.w.copy())
-
- companion object {
- fun of(vararg a: Float): Matrix4 {
- require(a.size >= 16)
- return Matrix4(
- Vector4(a[0], a[4], a[8], a[12]),
- Vector4(a[1], a[5], a[9], a[13]),
- Vector4(a[2], a[6], a[10], a[14]),
- Vector4(a[3], a[7], a[11], a[15])
- )
- }
-
- fun zero() = diagonal3Values(0.0f, 0.0f, 0.0f)
-
- fun identity() = Matrix4()
-
- fun diagonal3(scale: Vector3): Matrix4 {
- return diagonal3Values(x = scale.x, y = scale.y, z = scale.z)
- }
-
- fun diagonal3Values(x: Float, y: Float, z: Float): Matrix4 {
- return Matrix4(
- Vector4(x, 0.0f, 0.0f, 0.0f),
- Vector4(0.0f, y, 0.0f, 0.0f),
- Vector4(0.0f, 0.0f, z, 0.0f),
- Vector4(0.0f, 0.0f, 0.0f, 1.0f)
- )
- }
-
- /** Rotation of [radians_] around X. */
- fun rotationX(radians: Float) = zero().apply {
- set(3, 3, 1.0f)
- rotateX(radians)
- }
-
- /** Rotation of [radians_] around Y. */
- fun rotationY(radians: Float) = zero().apply {
- set(3, 3, 1.0f)
- rotateY(radians)
- }
-
- fun rotationZ(radians: Float) = zero().apply {
- set(3, 3, 1.0f)
- rotateZ(radians)
- }
-
- // / Translation matrix.
- fun translation(translation: Vector3) = identity().apply {
- setTranslationRaw(x = translation.x, y = translation.y, z = translation.z)
- }
-
- fun translationValues(x: Float, y: Float, z: Float) =
- identity().apply { setTranslationRaw(x, y, z) }
- }
-
- inline val m4storage: List<Float>
- get() = x.v4storage + y.v4storage + z.v4storage + w.v4storage
-
- inline var right: Vector3
- get() = x.xyz
- set(value) {
- x.xyz = value
- }
- inline var up: Vector3
- get() = y.xyz
- set(value) {
- y.xyz = value
- }
- inline var forward: Vector3
- get() = z.xyz
- set(value) {
- z.xyz = value
- }
- inline var position: Vector3
- get() = w.xyz
- set(value) {
- w.xyz = value
- }
-
- inline val scale: Vector3
- get() = Vector3(length(x.xyz), length(y.xyz), length(z.xyz))
- inline val translation: Vector3
- get() = w.xyz
- val rotation: Vector3
- get() {
- val x = normalize(right)
- val y = normalize(up)
- val z = normalize(forward)
-
- return when {
- z.y <= -1.0f -> Vector3(degrees(-HALF_PI), 0.0f, degrees(atan2(x.z, y.z)))
- z.y >= 1.0f -> Vector3(degrees(HALF_PI), 0.0f, degrees(atan2(-x.z, -y.z)))
- else -> Vector3(
- degrees(-asin(z.y)), degrees(-atan2(z.x, z.z)), degrees(atan2(x.y, y.y))
- )
- }
- }
-
- inline val upperLeft: Matrix3
- get() = Matrix3(x.xyz, y.xyz, z.xyz)
-
- operator fun get(column: Int) = when (column) {
- 0 -> x
- 1 -> y
- 2 -> z
- 3 -> w
- else -> throw IllegalArgumentException("column must be in 0..3")
- }
-
- operator fun get(column: Int, row: Int) = get(column)[row]
-
- operator fun get(column: MatrixColumn) = when (column) {
- MatrixColumn.X -> x
- MatrixColumn.Y -> y
- MatrixColumn.Z -> z
- MatrixColumn.W -> w
- }
-
- operator fun get(column: MatrixColumn, row: Int) = get(column)[row]
-
- fun getRow(row: Int): Vector4 {
- return Vector4(x[row], y[row], z[row], w[row])
- }
-
- operator fun set(column: Int, v: Vector4) {
- this[column].xyzw = v
- }
-
- operator fun set(column: Int, row: Int, v: Float) {
- this[column][row] = v
- }
-
- operator fun unaryMinus() = Matrix4(-x, -y, -z, -w)
- operator fun inc() = Matrix4(this).apply {
- ++x
- ++y
- ++z
- ++w
- }
-
- operator fun dec() = Matrix4(this).apply {
- --x
- --y
- --z
- --w
- }
-
- operator fun plus(v: Float) = Matrix4(x + v, y + v, z + v, w + v)
- operator fun minus(v: Float) = Matrix4(x - v, y - v, z - v, w - v)
- operator fun times(v: Float) = Matrix4(x * v, y * v, z * v, w * v)
- operator fun div(v: Float) = Matrix4(x / v, y / v, z / v, w / v)
-
- operator fun times(m: Matrix4): Matrix4 {
- val t = transpose(this)
- return Matrix4(
- Vector4(dot(t.x, m.x), dot(t.y, m.x), dot(t.z, m.x), dot(t.w, m.x)),
- Vector4(dot(t.x, m.y), dot(t.y, m.y), dot(t.z, m.y), dot(t.w, m.y)),
- Vector4(dot(t.x, m.z), dot(t.y, m.z), dot(t.z, m.z), dot(t.w, m.z)),
- Vector4(dot(t.x, m.w), dot(t.y, m.w), dot(t.z, m.w), dot(t.w, m.w))
- )
- }
-
- operator fun times(v: Vector4): Vector4 {
- val t = transpose(this)
- return Vector4(dot(t.x, v), dot(t.y, v), dot(t.z, v), dot(t.w, v))
- }
-
- operator fun timesAssign(m: Matrix4) {
- assignColumns(this * m)
- }
-
- fun assignColumns(other: Matrix4) {
- this.x = other.x
- this.y = other.y
- this.z = other.z
- this.w = other.w
- }
-
- fun assignFromStorage(storage: List<Float>) {
- check(storage.size == 16)
- x.assignFromStorage(storage.subList(0, 4))
- y.assignFromStorage(storage.subList(4, 8))
- z.assignFromStorage(storage.subList(8, 12))
- w.assignFromStorage(storage.subList(12, 16))
- }
-
- fun toFloatArray() = floatArrayOf(
- x.x, y.x, z.x, w.x,
- x.y, y.y, z.y, w.y,
- x.z, y.z, z.z, w.z,
- x.w, y.w, z.w, w.w
- )
-
- override fun toString(): String {
- return """
- |${x.x} ${y.x} ${z.x} ${w.x}|
- |${x.y} ${y.y} ${z.y} ${w.y}|
- |${x.z} ${y.z} ${z.z} ${w.z}|
- |${x.w} ${y.w} ${z.w} ${w.w}|
- """.trimIndent()
- }
-
- // ***** Required methods from dart's matrix4 *****
-
- /**
- * Transform [arg] of type [Vector3] using the perspective transformation
- * defined by [this].
- */
- fun perspectiveTransform(arg: Vector3): Vector3 {
- val argStorage = arg.v3storage
- val x_ = m4storage[0] * argStorage[0] +
- m4storage[4] * argStorage[1] +
- m4storage[8] * argStorage[2] +
- m4storage[12]
- val y_ = m4storage[1] * argStorage[0] +
- m4storage[5] * argStorage[1] +
- m4storage[9] * argStorage[2] +
- m4storage[13]
- val z_ = m4storage[2] * argStorage[0] +
- m4storage[6] * argStorage[1] +
- m4storage[10] * argStorage[2] +
- m4storage[14]
- val w_ = 1.0f / (m4storage[3] * argStorage[0] +
- m4storage[7] * argStorage[1] +
- m4storage[11] * argStorage[2] +
- m4storage[15])
- arg.x = x_ * w_
- arg.y = y_ * w_
- arg.z = z_ * w_
- return arg
- }
-
- /** Returns the determinant of this matrix. */
- val determinant: Float
- get() {
- val det2_01_01 = m4storage[0] * m4storage[5] - m4storage[1] * m4storage[4]
- val det2_01_02 = m4storage[0] * m4storage[6] - m4storage[2] * m4storage[4]
- val det2_01_03 = m4storage[0] * m4storage[7] - m4storage[3] * m4storage[4]
- val det2_01_12 = m4storage[1] * m4storage[6] - m4storage[2] * m4storage[5]
- val det2_01_13 = m4storage[1] * m4storage[7] - m4storage[3] * m4storage[5]
- val det2_01_23 = m4storage[2] * m4storage[7] - m4storage[3] * m4storage[6]
- val det3_201_012 = m4storage[8] * det2_01_12 - m4storage[9] * det2_01_02 +
- m4storage[10] * det2_01_01
- val det3_201_013 = m4storage[8] * det2_01_13 - m4storage[9] * det2_01_03 +
- m4storage[11] * det2_01_01
- val det3_201_023 = m4storage[8] * det2_01_23 - m4storage[10] * det2_01_03 +
- m4storage[11] * det2_01_02
- val det3_201_123 = m4storage[9] * det2_01_23 - m4storage[10] * det2_01_13 +
- m4storage[11] * det2_01_12
- return -det3_201_123 * m4storage[12] + det3_201_023 * m4storage[13] -
- det3_201_013 * m4storage[14] + det3_201_012 * m4storage[15]
- }
-
- /** Invert [this]. */
- fun invert() = copyInverse(this)
-
- /** Set this matrix to be the inverse of [arg] */
- fun copyInverse(arg: Matrix4): Float {
- val argStorage = arg.m4storage
- val a00 = argStorage[0]
- val a01 = argStorage[1]
- val a02 = argStorage[2]
- val a03 = argStorage[3]
- val a10 = argStorage[4]
- val a11 = argStorage[5]
- val a12 = argStorage[6]
- val a13 = argStorage[7]
- val a20 = argStorage[8]
- val a21 = argStorage[9]
- val a22 = argStorage[10]
- val a23 = argStorage[11]
- val a30 = argStorage[12]
- val a31 = argStorage[13]
- val a32 = argStorage[14]
- val a33 = argStorage[15]
- val b00 = a00 * a11 - a01 * a10
- val b01 = a00 * a12 - a02 * a10
- val b02 = a00 * a13 - a03 * a10
- val b03 = a01 * a12 - a02 * a11
- val b04 = a01 * a13 - a03 * a11
- val b05 = a02 * a13 - a03 * a12
- val b06 = a20 * a31 - a21 * a30
- val b07 = a20 * a32 - a22 * a30
- val b08 = a20 * a33 - a23 * a30
- val b09 = a21 * a32 - a22 * a31
- val b10 = a21 * a33 - a23 * a31
- val b11 = a22 * a33 - a23 * a32
- val det =
- (b00 * b11 - b01 * b10 + b02 * b09 + b03 * b08 - b04 * b07 + b05 * b06)
- if (det == 0.0f) {
- setFrom(arg)
- return 0.0f
- }
- val invDet = 1.0f / det
- val newStorage = MutableList(16) { 0.0f }
- newStorage[0] = ((a11 * b11 - a12 * b10 + a13 * b09) * invDet)
- newStorage[1] = ((-a01 * b11 + a02 * b10 - a03 * b09) * invDet)
- newStorage[2] = ((a31 * b05 - a32 * b04 + a33 * b03) * invDet)
- newStorage[3] = ((-a21 * b05 + a22 * b04 - a23 * b03) * invDet)
- newStorage[4] = ((-a10 * b11 + a12 * b08 - a13 * b07) * invDet)
- newStorage[5] = ((a00 * b11 - a02 * b08 + a03 * b07) * invDet)
- newStorage[6] = ((-a30 * b05 + a32 * b02 - a33 * b01) * invDet)
- newStorage[7] = ((a20 * b05 - a22 * b02 + a23 * b01) * invDet)
- newStorage[8] = ((a10 * b10 - a11 * b08 + a13 * b06) * invDet)
- newStorage[9] = ((-a00 * b10 + a01 * b08 - a03 * b06) * invDet)
- newStorage[10] = ((a30 * b04 - a31 * b02 + a33 * b00) * invDet)
- newStorage[11] = ((-a20 * b04 + a21 * b02 - a23 * b00) * invDet)
- newStorage[12] = ((-a10 * b09 + a11 * b07 - a12 * b06) * invDet)
- newStorage[13] = ((a00 * b09 - a01 * b07 + a02 * b06) * invDet)
- newStorage[14] = ((-a30 * b03 + a31 * b01 - a32 * b00) * invDet)
- newStorage[15] = ((a20 * b03 - a21 * b01 + a22 * b00) * invDet)
- assignFromStorage(newStorage)
- return det
- }
-
- /** Sets the entire matrix to the matrix in [arg]. */
- fun setFrom(arg: Matrix4) {
- assignFromStorage(arg.m4storage)
- }
-
- fun rotateX(radians: Float) {
- val c = cos(radians)
- val s = sin(radians)
- val newStorage = m4storage.toMutableList()
- newStorage[0] = 1.0f
- newStorage[1] = 0.0f
- newStorage[2] = 0.0f
- newStorage[4] = 0.0f
- newStorage[5] = c
- newStorage[6] = s
- newStorage[8] = 0.0f
- newStorage[9] = -s
- newStorage[10] = c
- newStorage[3] = 0.0f
- newStorage[7] = 0.0f
- newStorage[11] = 0.0f
- assignFromStorage(newStorage)
- }
-
- /** Sets the upper 3x3 to a rotation of [radians] around Y */
- fun rotateY(radians: Float) {
- val c = cos(radians)
- val s = sin(radians)
- val newStorage = m4storage.toMutableList()
- newStorage[0] = c
- newStorage[1] = 0.0f
- newStorage[2] = -s
- newStorage[4] = 0.0f
- newStorage[5] = 1.0f
- newStorage[6] = 0.0f
- newStorage[8] = s
- newStorage[9] = 0.0f
- newStorage[10] = c
- newStorage[3] = 0.0f
- newStorage[7] = 0.0f
- newStorage[11] = 0.0f
- assignFromStorage(newStorage)
- }
-
- /** Sets the upper 3x3 to a rotation of [radians] around Z */
- fun rotateZ(radians: Float) {
- val c = cos(radians)
- val s = sin(radians)
- val newStorage = m4storage.toMutableList()
- newStorage[0] = c
- newStorage[1] = s
- newStorage[2] = 0.0f
- newStorage[4] = -s
- newStorage[5] = c
- newStorage[6] = 0.0f
- newStorage[8] = 0.0f
- newStorage[9] = 0.0f
- newStorage[10] = 1.0f
- newStorage[3] = 0.0f
- newStorage[7] = 0.0f
- newStorage[11] = 0.0f
- assignFromStorage(newStorage)
- }
-
- /** Sets the translation vector in this homogeneous transformation matrix. */
- fun setTranslationRaw(x: Float, y: Float, z: Float) {
- set(3, 0, x)
- set(3, 1, y)
- set(3, 2, z)
- }
-
- /** Scale this matrix by a [Vector3], [Vector4], or x,y,z */
- fun scale(x: Any, y: Float? = null, z: Float? = null) {
- var sx: Float? = null
- var sy: Float? = null
- var sz: Float? = null
- val sw = if (x is Vector4) x.w else 1.0f
- if (x is Vector3) {
- sx = x.x
- sy = x.y
- sz = x.z
- } else if (x is Vector4) {
- sx = x.x
- sy = x.y
- sz = x.z
- } else if (x is Float) {
- sx = x
- sy = if (y != null) y else x
- sz = if (z != null) z else x
- }
- sx as Float
- sy as Float
- sz as Float
- val newStorage = m4storage.toMutableList()
- newStorage[0] *= sx
- newStorage[1] *= sx
- newStorage[2] *= sx
- newStorage[3] *= sx
- newStorage[4] *= sy
- newStorage[5] *= sy
- newStorage[6] *= sy
- newStorage[7] *= sy
- newStorage[8] *= sz
- newStorage[9] *= sz
- newStorage[10] *= sz
- newStorage[11] *= sz
- newStorage[12] *= sw
- newStorage[13] *= sw
- newStorage[14] *= sw
- newStorage[15] *= sw
- assignFromStorage(newStorage)
- }
-
- /** Translate this matrix by a [Vector3], [Vector4], or x,y,z */
- fun translate(x: Any, y: Float = 0.0f, z: Float = 0.0f) {
- var tx: Float? = null
- var ty: Float? = null
- var tz: Float? = null
- var tw = if (x is Vector4) x.w else 1.0f
- if (x is Vector3) {
- tx = x.x
- ty = x.y
- tz = x.z
- } else if (x is Vector4) {
- tx = x.x
- ty = x.y
- tz = x.z
- } else if (x is Float) {
- tx = x
- ty = y
- tz = z
- }
- tx as Float
- ty as Float
- tz as Float
- val newStorage = m4storage.toMutableList()
- val t1 = newStorage[0] * tx +
- newStorage[4] * ty +
- newStorage[8] * tz +
- newStorage[12] * tw
- val t2 = newStorage[1] * tx +
- newStorage[5] * ty +
- newStorage[9] * tz +
- newStorage[13] * tw
- val t3 = newStorage[2] * tx +
- newStorage[6] * ty +
- newStorage[10] * tz +
- newStorage[14] * tw
- val t4 = newStorage[3] * tx +
- newStorage[7] * ty +
- newStorage[11] * tz +
- newStorage[15] * tw
- newStorage[12] = t1
- newStorage[13] = t2
- newStorage[14] = t3
- newStorage[15] = t4
- assignFromStorage(newStorage)
- }
-}
-
-/**
- * Returns the given [transform] matrix as an [Offset], if the matrix is
- * nothing but a 2D translation.
- *
- * Otherwise, returns null.
- */
-fun Matrix4.getAsTranslation(): Offset? {
- val values = m4storage
- // Values are stored in column-major order.
- return if (values[0] == 1.0f && // col 1
- values[1] == 0.0f &&
- values[2] == 0.0f &&
- values[3] == 0.0f &&
- values[4] == 0.0f && // col 2
- values[5] == 1.0f &&
- values[6] == 0.0f &&
- values[7] == 0.0f &&
- values[8] == 0.0f && // col 3
- values[9] == 0.0f &&
- values[10] == 1.0f &&
- values[11] == 0.0f &&
- values[14] == 0.0f && // bottom of col 4 (values 12 and 13 are the x and y offsets)
-
- values[15] == 1.0f) {
- Offset(values[12], values[13])
- } else null
-}
-
-/**
- * Returns the given [transform] matrix as a [Float] describing a uniform
- * scale, if the matrix is nothing but a symmetric 2D scale transform.
- *
- * Otherwise, returns null.
- */
-fun Matrix4.getAsScale(): Float? {
- val values = m4storage
- // Values are stored in column-major order.
- return if (values[1] == 0.0f && // col 1 (value 0 is the scale)
- values[2] == 0.0f &&
- values[3] == 0.0f &&
- values[4] == 0.0f && // col 2 (value 5 is the scale)
- values[6] == 0.0f &&
- values[7] == 0.0f &&
- values[8] == 0.0f && // col 3
- values[9] == 0.0f &&
- values[10] == 1.0f &&
- values[11] == 0.0f &&
- values[12] == 0.0f && // col 4
- values[13] == 0.0f &&
- values[14] == 0.0f &&
- values[15] == 1.0f &&
- values[0] == values[5]) { // uniform scale
- values[0]
- } else null
-}
-
-/**
- * Returns true if the given matrices are exactly equal, and false
- * otherwise. Null values are assumed to be the identity matrix.
- */
-fun matrixEquals(a: Matrix4?, b: Matrix4?): Boolean {
- if (a === b)
- return true
- check(a != null || b != null)
- if (a == null)
- return b!!.isIdentity()
- if (b == null) {
- return a.isIdentity()
- }
- val astorage = a.m4storage
- val bstorage = b.m4storage
- return astorage.subList(0, 16) == bstorage.subList(0, 16)
-}
-
-/** Whether the given matrix is the identity matrix. */
-fun Matrix4.isIdentity(): Boolean {
- val storage = m4storage
- return (storage[0] == 1.0f && // col 1
- storage[1] == 0.0f &&
- storage[2] == 0.0f &&
- storage[3] == 0.0f &&
- storage[4] == 0.0f && // col 2
- storage[5] == 1.0f &&
- storage[6] == 0.0f &&
- storage[7] == 0.0f &&
- storage[8] == 0.0f && // col 3
- storage[9] == 0.0f &&
- storage[10] == 1.0f &&
- storage[11] == 0.0f &&
- storage[12] == 0.0f && // col 4
- storage[13] == 0.0f &&
- storage[14] == 0.0f &&
- storage[15] == 1.0f)
-}
-
-/**
- * Applies the given matrix as a perspective transform to the given point.
- *
- * this function assumes the given point has a z-coordinate of 0.0. the
- * z-coordinate of the result is ignored.
- */
-fun Matrix4.transformPoint(point: Offset): Offset {
- val position3 = Vector3(point.x, point.y, 0.0f)
- val transformed3 = perspectiveTransform(position3)
- return Offset(transformed3.x, transformed3.y)
-}
-
-/**
- * Returns a rect that bounds the result of applying the given matrix as a
- * perspective transform to the given rect.
- *
- * This function assumes the given rect is in the plane with z equals 0.0.
- * The transformed rect is then projected back into the plane with z equals
- * 0.0 before computing its bounding rect.
- */
-fun Matrix4.transformRect(rect: Rect): Rect {
- val point1 = transformPoint(rect.topLeft)
- val point2 = transformPoint(rect.topRight)
- val point3 = transformPoint(rect.bottomLeft)
- val point4 = transformPoint(rect.bottomRight)
- return Rect(
- min4(point1.x, point2.x, point3.x, point4.x),
- min4(point1.y, point2.y, point3.y, point4.y),
- max4(point1.x, point2.x, point3.x, point4.x),
- max4(point1.y, point2.y, point3.y, point4.y)
- )
-}
-
-private fun min4(a: Float, b: Float, c: Float, d: Float): Float {
- return minOf(a, minOf(b, minOf(c, d)))
-}
-
-private fun max4(a: Float, b: Float, c: Float, d: Float): Float {
- return maxOf(a, maxOf(b, maxOf(c, d)))
-}
-
-/**
- * Returns a rect that bounds the result of applying the inverse of the given
- * matrix as a perspective transform to the given rect.
- *
- * This function assumes the given rect is in the plane with z equals 0.0.
- * The transformed rect is then projected back into the plane with z equals
- * 0.0 before computing its bounding rect.
- */
-fun inverseTransformRect(transform: Matrix4, rect: Rect): Rect {
- check(transform.determinant != 0.0f)
- if (transform.isIdentity())
- return rect
- val inverted = Matrix4(transform).apply { invert() }
- return inverted.transformRect(rect)
-}
diff --git a/compose/ui/ui-graphics/src/commonMain/kotlin/androidx/compose/ui/graphics/vectormath/MatrixColumn.kt b/compose/ui/ui-graphics/src/commonMain/kotlin/androidx/compose/ui/graphics/vectormath/MatrixColumn.kt
deleted file mode 100644
index b1c218a..0000000
--- a/compose/ui/ui-graphics/src/commonMain/kotlin/androidx/compose/ui/graphics/vectormath/MatrixColumn.kt
+++ /dev/null
@@ -1,20 +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.compose.ui.graphics.vectormath
-
-enum class MatrixColumn {
- X, Y, Z, W
-}
diff --git a/compose/ui/ui-graphics/src/commonMain/kotlin/androidx/compose/ui/graphics/vectormath/MatrixExtensions.kt b/compose/ui/ui-graphics/src/commonMain/kotlin/androidx/compose/ui/graphics/vectormath/MatrixExtensions.kt
deleted file mode 100644
index da5d59a..0000000
--- a/compose/ui/ui-graphics/src/commonMain/kotlin/androidx/compose/ui/graphics/vectormath/MatrixExtensions.kt
+++ /dev/null
@@ -1,190 +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.compose.ui.graphics.vectormath
-
-import kotlin.math.cos
-import kotlin.math.sin
-import kotlin.math.tan
-
-fun transpose(m: Matrix3) = Matrix3(
- Vector3(m.x.x, m.y.x, m.z.x),
- Vector3(m.x.y, m.y.y, m.z.y),
- Vector3(m.x.z, m.y.z, m.z.z)
-)
-fun inverse(m: Matrix3): Matrix3 {
- val a = m.x.x
- val b = m.x.y
- val c = m.x.z
- val d = m.y.x
- val e = m.y.y
- val f = m.y.z
- val g = m.z.x
- val h = m.z.y
- val i = m.z.z
-
- val A = e * i - f * h
- val B = f * g - d * i
- val C = d * h - e * g
-
- val det = a * A + b * B + c * C
-
- return Matrix3.of(
- A / det, B / det, C / det,
- (c * h - b * i) / det, (a * i - c * g) / det, (b * g - a * h) / det,
- (b * f - c * e) / det, (c * d - a * f) / det, (a * e - b * d) / det
- )
-}
-
-fun transpose(m: Matrix4) = Matrix4(
- Vector4(m.x.x, m.y.x, m.z.x, m.w.x),
- Vector4(m.x.y, m.y.y, m.z.y, m.w.y),
- Vector4(m.x.z, m.y.z, m.z.z, m.w.z),
- Vector4(m.x.w, m.y.w, m.z.w, m.w.w)
-)
-fun inverse(m: Matrix4): Matrix4 {
- val result = Matrix4()
-
- var pair0 = m.z.z * m.w.w
- var pair1 = m.w.z * m.z.w
- var pair2 = m.y.z * m.w.w
- var pair3 = m.w.z * m.y.w
- var pair4 = m.y.z * m.z.w
- var pair5 = m.z.z * m.y.w
- var pair6 = m.x.z * m.w.w
- var pair7 = m.w.z * m.x.w
- var pair8 = m.x.z * m.z.w
- var pair9 = m.z.z * m.x.w
- var pair10 = m.x.z * m.y.w
- var pair11 = m.y.z * m.x.w
-
- result.x.x = pair0 * m.y.y + pair3 * m.z.y + pair4 * m.w.y
- result.x.x -= pair1 * m.y.y + pair2 * m.z.y + pair5 * m.w.y
- result.x.y = pair1 * m.x.y + pair6 * m.z.y + pair9 * m.w.y
- result.x.y -= pair0 * m.x.y + pair7 * m.z.y + pair8 * m.w.y
- result.x.z = pair2 * m.x.y + pair7 * m.y.y + pair10 * m.w.y
- result.x.z -= pair3 * m.x.y + pair6 * m.y.y + pair11 * m.w.y
- result.x.w = pair5 * m.x.y + pair8 * m.y.y + pair11 * m.z.y
- result.x.w -= pair4 * m.x.y + pair9 * m.y.y + pair10 * m.z.y
- result.y.x = pair1 * m.y.x + pair2 * m.z.x + pair5 * m.w.x
- result.y.x -= pair0 * m.y.x + pair3 * m.z.x + pair4 * m.w.x
- result.y.y = pair0 * m.x.x + pair7 * m.z.x + pair8 * m.w.x
- result.y.y -= pair1 * m.x.x + pair6 * m.z.x + pair9 * m.w.x
- result.y.z = pair3 * m.x.x + pair6 * m.y.x + pair11 * m.w.x
- result.y.z -= pair2 * m.x.x + pair7 * m.y.x + pair10 * m.w.x
- result.y.w = pair4 * m.x.x + pair9 * m.y.x + pair10 * m.z.x
- result.y.w -= pair5 * m.x.x + pair8 * m.y.x + pair11 * m.z.x
-
- pair0 = m.z.x * m.w.y
- pair1 = m.w.x * m.z.y
- pair2 = m.y.x * m.w.y
- pair3 = m.w.x * m.y.y
- pair4 = m.y.x * m.z.y
- pair5 = m.z.x * m.y.y
- pair6 = m.x.x * m.w.y
- pair7 = m.w.x * m.x.y
- pair8 = m.x.x * m.z.y
- pair9 = m.z.x * m.x.y
- pair10 = m.x.x * m.y.y
- pair11 = m.y.x * m.x.y
-
- result.z.x = pair0 * m.y.w + pair3 * m.z.w + pair4 * m.w.w
- result.z.x -= pair1 * m.y.w + pair2 * m.z.w + pair5 * m.w.w
- result.z.y = pair1 * m.x.w + pair6 * m.z.w + pair9 * m.w.w
- result.z.y -= pair0 * m.x.w + pair7 * m.z.w + pair8 * m.w.w
- result.z.z = pair2 * m.x.w + pair7 * m.y.w + pair10 * m.w.w
- result.z.z -= pair3 * m.x.w + pair6 * m.y.w + pair11 * m.w.w
- result.z.w = pair5 * m.x.w + pair8 * m.y.w + pair11 * m.z.w
- result.z.w -= pair4 * m.x.w + pair9 * m.y.w + pair10 * m.z.w
- result.w.x = pair2 * m.z.z + pair5 * m.w.z + pair1 * m.y.z
- result.w.x -= pair4 * m.w.z + pair0 * m.y.z + pair3 * m.z.z
- result.w.y = pair8 * m.w.z + pair0 * m.x.z + pair7 * m.z.z
- result.w.y -= pair6 * m.z.z + pair9 * m.w.z + pair1 * m.x.z
- result.w.z = pair6 * m.y.z + pair11 * m.w.z + pair3 * m.x.z
- result.w.z -= pair10 * m.w.z + pair2 * m.x.z + pair7 * m.y.z
- result.w.w = pair10 * m.z.z + pair4 * m.x.z + pair9 * m.y.z
- result.w.w -= pair8 * m.y.z + pair11 * m.z.z + pair5 * m.x.z
-
- val determinant = m.x.x * result.x.x + m.y.x * result.x.y +
- m.z.x * result.x.z + m.w.x * result.x.w
-
- return result / determinant
-}
-
-fun scale(s: Vector3) = Matrix4(Vector4(x = s.x), Vector4(y = s.y), Vector4(z = s.z))
-fun scale(m: Matrix4) = scale(m.scale)
-
-fun translation(t: Vector3) = Matrix4(w = Vector4(t, 1.0f))
-fun translation(m: Matrix4) = translation(m.translation)
-
-fun rotation(m: Matrix4) = Matrix4(normalize(m.right), normalize(m.up), normalize(m.forward))
-fun rotation(d: Vector3): Matrix4 {
- val r = transform(d, ::radians)
- val c = transform(r, { x -> cos(x) })
- val s = transform(r, { x -> sin(x) })
-
- return Matrix4.of(
- c.y * c.z, -c.x * s.z + s.x * s.y * c.z, s.x * s.z + c.x * s.y * c.z, 0.0f,
- c.y * s.z, c.x * c.z + s.x * s.y * s.z, -s.x * c.z + c.x * s.y * s.z, 0.0f,
- -s.y, s.x * c.y, c.x * c.y, 0.0f,
- 0.0f, 0.0f, 0.0f, 1.0f
- )
-}
-fun rotation(axis: Vector3, angle: Float): Matrix4 {
- val x = axis.x
- val y = axis.y
- val z = axis.z
-
- val r = radians(angle)
- val c = cos(r)
- val s = sin(r)
- val d = 1.0f - c
-
- return Matrix4.of(
- x * x * d + c, x * y * d - z * s, x * y * d + y * s, 0.0f,
- y * x * d + z * s, y * y * d + c, y * z * d - x * s, 0.0f,
- z * x * d - y * s, z * y * d + x * s, z * z * d + c, 0.0f,
- 0.0f, 0.0f, 0.0f, 1.0f
- )
-}
-
-fun normal(m: Matrix4) = scale(1.0f / Vector3(length2(m.right),
- length2(m.up), length2(m.forward))) * m
-
-fun lookAt(eye: Vector3, target: Vector3, up: Vector3 = Vector3(z = 1.0f)): Matrix4 {
- return lookTowards(eye, target - eye, up)
-}
-
-fun lookTowards(eye: Vector3, forward: Vector3, up: Vector3 = Vector3(z = 1.0f)): Matrix4 {
- val f = normalize(forward)
- val r = normalize(f x up)
- val u = normalize(r x f)
- return Matrix4(Vector4(r), Vector4(u), Vector4(f), Vector4(eye, 1.0f))
-}
-
-fun perspective(fov: Float, ratio: Float, near: Float, far: Float): Matrix4 {
- val t = 1.0f / tan(radians(fov) * 0.5f)
- val a = (far + near) / (far - near)
- val b = (2.0f * far * near) / (far - near)
- val c = t / ratio
- return Matrix4(Vector4(x = c), Vector4(y = t), Vector4(z = a, w = 1.0f), Vector4(z = -b))
-}
-
-fun ortho(l: Float, r: Float, b: Float, t: Float, n: Float, f: Float) = Matrix4(
- Vector4(x = 2.0f / (r - 1.0f)),
- Vector4(y = 2.0f / (t - b)),
- Vector4(z = -2.0f / (f - n)),
- Vector4(-(r + l) / (r - l), -(t + b) / (t - b), -(f + n) / (f - n), 1.0f)
-)
diff --git a/compose/ui/ui-graphics/src/commonMain/kotlin/androidx/compose/ui/graphics/vectormath/Scalar.kt b/compose/ui/ui-graphics/src/commonMain/kotlin/androidx/compose/ui/graphics/vectormath/Scalar.kt
deleted file mode 100644
index 8da6268..0000000
--- a/compose/ui/ui-graphics/src/commonMain/kotlin/androidx/compose/ui/graphics/vectormath/Scalar.kt
+++ /dev/null
@@ -1,30 +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.
- */
-@file:Suppress("NOTHING_TO_INLINE")
-
-package androidx.compose.ui.graphics.vectormath
-
-const val PI = 3.1415926536f
-const val HALF_PI = PI * 0.5f
-const val TWO_PI = PI * 2.0f
-const val FOUR_PI = PI * 4.0f
-const val INV_PI = 1.0f / PI
-const val INV_TWO_PI = INV_PI * 0.5f
-const val INV_FOUR_PI = INV_PI * 0.25f
-
-inline fun degrees(v: Float) = v * (180.0f * INV_PI)
-
-inline fun radians(v: Float) = v * (PI / 180.0f)
diff --git a/compose/ui/ui-graphics/src/commonMain/kotlin/androidx/compose/ui/graphics/vectormath/Vector2.kt b/compose/ui/ui-graphics/src/commonMain/kotlin/androidx/compose/ui/graphics/vectormath/Vector2.kt
deleted file mode 100644
index 3ead534..0000000
--- a/compose/ui/ui-graphics/src/commonMain/kotlin/androidx/compose/ui/graphics/vectormath/Vector2.kt
+++ /dev/null
@@ -1,131 +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.
- */
-@file:Suppress("NOTHING_TO_INLINE")
-package androidx.compose.ui.graphics.vectormath
-
-data class Vector2(var x: Float = 0.0f, var y: Float = 0.0f) {
- constructor(v: Vector2) : this(v.x, v.y)
-
- inline val v2storage: List<Float>
- get() = listOf(x, y)
-
- inline var r: Float
- get() = x
- set(value) {
- x = value
- }
- inline var g: Float
- get() = y
- set(value) {
- y = value
- }
-
- inline var s: Float
- get() = x
- set(value) {
- x = value
- }
- inline var t: Float
- get() = y
- set(value) {
- y = value
- }
-
- inline var xy: Vector2
- get() = Vector2(x, y)
- set(value) {
- x = value.x
- y = value.y
- }
- inline var rg: Vector2
- get() = Vector2(x, y)
- set(value) {
- x = value.x
- y = value.y
- }
- inline var st: Vector2
- get() = Vector2(x, y)
- set(value) {
- x = value.x
- y = value.y
- }
-
- operator fun get(index: VectorComponent) = when (index) {
- VectorComponent.X, VectorComponent.R, VectorComponent.S -> x
- VectorComponent.Y, VectorComponent.G, VectorComponent.T -> y
- else -> throw IllegalArgumentException("index must be X, Y, R, G, S or T")
- }
-
- operator fun get(index1: VectorComponent, index2: VectorComponent): Vector2 {
- return Vector2(get(index1), get(index2))
- }
-
- operator fun get(index: Int) = when (index) {
- 0 -> x
- 1 -> y
- else -> throw IllegalArgumentException("index must be in 0..1")
- }
-
- operator fun get(index1: Int, index2: Int) = Vector2(get(index1), get(index2))
-
- operator fun set(index: Int, v: Float) = when (index) {
- 0 -> x = v
- 1 -> y = v
- else -> throw IllegalArgumentException("index must be in 0..1")
- }
-
- operator fun set(index1: Int, index2: Int, v: Float) {
- set(index1, v)
- set(index2, v)
- }
-
- operator fun set(index: VectorComponent, v: Float) = when (index) {
- VectorComponent.X, VectorComponent.R, VectorComponent.S -> x = v
- VectorComponent.Y, VectorComponent.G, VectorComponent.T -> y = v
- else -> throw IllegalArgumentException("index must be X, Y, R, G, S or T")
- }
-
- operator fun set(index1: VectorComponent, index2: VectorComponent, v: Float) {
- set(index1, v)
- set(index2, v)
- }
-
- operator fun unaryMinus() = Vector2(-x, -y)
- operator fun inc() = Vector2(this).apply {
- ++x
- ++y
- }
- operator fun dec() = Vector2(this).apply {
- --x
- --y
- }
-
- inline operator fun plus(v: Float) = Vector2(x + v, y + v)
- inline operator fun minus(v: Float) = Vector2(x - v, y - v)
- inline operator fun times(v: Float) = Vector2(x * v, y * v)
- inline operator fun div(v: Float) = Vector2(x / v, y / v)
-
- inline operator fun plus(v: Vector2) = Vector2(x + v.x, y + v.y)
- inline operator fun minus(v: Vector2) = Vector2(x - v.x, y - v.y)
- inline operator fun times(v: Vector2) = Vector2(x * v.x, y * v.y)
- inline operator fun div(v: Vector2) = Vector2(x / v.x, y / v.y)
-
- inline fun transform(block: (Float) -> Float): Vector2 {
- x = block(x)
- y = block(y)
- return this
- }
-}
diff --git a/compose/ui/ui-graphics/src/commonMain/kotlin/androidx/compose/ui/graphics/vectormath/Vector3.kt b/compose/ui/ui-graphics/src/commonMain/kotlin/androidx/compose/ui/graphics/vectormath/Vector3.kt
deleted file mode 100644
index 548ae7d..0000000
--- a/compose/ui/ui-graphics/src/commonMain/kotlin/androidx/compose/ui/graphics/vectormath/Vector3.kt
+++ /dev/null
@@ -1,203 +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.
- */
-@file:Suppress("NOTHING_TO_INLINE")
-package androidx.compose.ui.graphics.vectormath
-
-data class Vector3(var x: Float = 0.0f, var y: Float = 0.0f, var z: Float = 0.0f) {
- constructor(v: Vector2, z: Float = 0.0f) : this(v.x, v.y, z)
- constructor(v: Vector3) : this(v.x, v.y, v.z)
-
- inline val v3storage: List<Float>
- get() = listOf(x, y, z)
-
- inline var r: Float
- get() = x
- set(value) {
- x = value
- }
- inline var g: Float
- get() = y
- set(value) {
- y = value
- }
- inline var b: Float
- get() = z
- set(value) {
- z = value
- }
-
- inline var s: Float
- get() = x
- set(value) {
- x = value
- }
- inline var t: Float
- get() = y
- set(value) {
- y = value
- }
- inline var p: Float
- get() = z
- set(value) {
- z = value
- }
-
- inline var xy: Vector2
- get() = Vector2(x, y)
- set(value) {
- x = value.x
- y = value.y
- }
- inline var rg: Vector2
- get() = Vector2(x, y)
- set(value) {
- x = value.x
- y = value.y
- }
- inline var st: Vector2
- get() = Vector2(x, y)
- set(value) {
- x = value.x
- y = value.y
- }
-
- inline var rgb: Vector3
- get() = Vector3(x, y, z)
- set(value) {
- x = value.x
- y = value.y
- z = value.z
- }
- inline var xyz: Vector3
- get() = Vector3(x, y, z)
- set(value) {
- x = value.x
- y = value.y
- z = value.z
- }
- inline var stp: Vector3
- get() = Vector3(x, y, z)
- set(value) {
- x = value.x
- y = value.y
- z = value.z
- }
-
- operator fun get(index: VectorComponent) = when (index) {
- VectorComponent.X, VectorComponent.R, VectorComponent.S -> x
- VectorComponent.Y, VectorComponent.G, VectorComponent.T -> y
- VectorComponent.Z, VectorComponent.B, VectorComponent.P -> z
- else -> throw IllegalArgumentException("index must be X, Y, Z, R, G, B, S, T or P")
- }
-
- operator fun get(index1: VectorComponent, index2: VectorComponent): Vector2 {
- return Vector2(get(index1), get(index2))
- }
- operator fun get(
- index1: VectorComponent,
- index2: VectorComponent,
- index3: VectorComponent
- ): Vector3 {
- return Vector3(get(index1), get(index2), get(index3))
- }
-
- operator fun get(index: Int) = when (index) {
- 0 -> x
- 1 -> y
- 2 -> z
- else -> throw IllegalArgumentException("index must be in 0..2")
- }
-
- operator fun get(index1: Int, index2: Int) = Vector2(get(index1), get(index2))
- operator fun get(index1: Int, index2: Int, index3: Int): Vector3 {
- return Vector3(get(index1), get(index2), get(index3))
- }
-
- operator fun set(index: Int, v: Float) = when (index) {
- 0 -> x = v
- 1 -> y = v
- 2 -> z = v
- else -> throw IllegalArgumentException("index must be in 0..2")
- }
-
- operator fun set(index1: Int, index2: Int, v: Float) {
- set(index1, v)
- set(index2, v)
- }
-
- operator fun set(index1: Int, index2: Int, index3: Int, v: Float) {
- set(index1, v)
- set(index2, v)
- set(index3, v)
- }
-
- operator fun set(index: VectorComponent, v: Float) = when (index) {
- VectorComponent.X, VectorComponent.R, VectorComponent.S -> x = v
- VectorComponent.Y, VectorComponent.G, VectorComponent.T -> y = v
- VectorComponent.Z, VectorComponent.B, VectorComponent.P -> z = v
- else -> throw IllegalArgumentException("index must be X, Y, Z, R, G, B, S, T or P")
- }
-
- operator fun set(index1: VectorComponent, index2: VectorComponent, v: Float) {
- set(index1, v)
- set(index2, v)
- }
-
- operator fun set(
- index1: VectorComponent,
- index2: VectorComponent,
- index3: VectorComponent,
- v: Float
- ) {
- set(index1, v)
- set(index2, v)
- set(index3, v)
- }
-
- operator fun unaryMinus() = Vector3(-x, -y, -z)
- operator fun inc() = Vector3(this).apply {
- ++x
- ++y
- ++z
- }
- operator fun dec() = Vector3(this).apply {
- --x
- --y
- --z
- }
-
- inline operator fun plus(v: Float) = Vector3(x + v, y + v, z + v)
- inline operator fun minus(v: Float) = Vector3(x - v, y - v, z - v)
- inline operator fun times(v: Float) = Vector3(x * v, y * v, z * v)
- inline operator fun div(v: Float) = Vector3(x / v, y / v, z / v)
-
- inline operator fun plus(v: Vector2) = Vector3(x + v.x, y + v.y, z)
- inline operator fun minus(v: Vector2) = Vector3(x - v.x, y - v.y, z)
- inline operator fun times(v: Vector2) = Vector3(x * v.x, y * v.y, z)
- inline operator fun div(v: Vector2) = Vector3(x / v.x, y / v.y, z)
-
- inline operator fun plus(v: Vector3) = Vector3(x + v.x, y + v.y, z + v.z)
- inline operator fun minus(v: Vector3) = Vector3(x - v.x, y - v.y, z - v.z)
- inline operator fun times(v: Vector3) = Vector3(x * v.x, y * v.y, z * v.z)
- inline operator fun div(v: Vector3) = Vector3(x / v.x, y / v.y, z / v.z)
-
- inline fun transform(block: (Float) -> Float): Vector3 {
- x = block(x)
- y = block(y)
- z = block(z)
- return this
- }
-}
diff --git a/compose/ui/ui-graphics/src/commonMain/kotlin/androidx/compose/ui/graphics/vectormath/Vector4.kt b/compose/ui/ui-graphics/src/commonMain/kotlin/androidx/compose/ui/graphics/vectormath/Vector4.kt
deleted file mode 100644
index 828b583..0000000
--- a/compose/ui/ui-graphics/src/commonMain/kotlin/androidx/compose/ui/graphics/vectormath/Vector4.kt
+++ /dev/null
@@ -1,295 +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.
- */
-@file:Suppress("NOTHING_TO_INLINE")
-package androidx.compose.ui.graphics.vectormath
-
-data class Vector4(
- var x: Float = 0.0f,
- var y: Float = 0.0f,
- var z: Float = 0.0f,
- var w: Float = 0.0f
-) {
- constructor(v: Vector2, z: Float = 0.0f, w: Float = 0.0f) : this(v.x, v.y, z, w)
- constructor(v: Vector3, w: Float = 0.0f) : this(v.x, v.y, v.z, w)
- constructor(v: Vector4) : this(v.x, v.y, v.z, v.w)
-
- inline val v4storage: List<Float>
- get() = listOf(x, y, z, w)
-
- inline var r: Float
- get() = x
- set(value) {
- x = value
- }
- inline var g: Float
- get() = y
- set(value) {
- y = value
- }
- inline var b: Float
- get() = z
- set(value) {
- z = value
- }
- inline var a: Float
- get() = w
- set(value) {
- w = value
- }
-
- inline var s: Float
- get() = x
- set(value) {
- x = value
- }
- inline var t: Float
- get() = y
- set(value) {
- y = value
- }
- inline var p: Float
- get() = z
- set(value) {
- z = value
- }
- inline var q: Float
- get() = w
- set(value) {
- w = value
- }
-
- inline var xy: Vector2
- get() = Vector2(x, y)
- set(value) {
- x = value.x
- y = value.y
- }
- inline var rg: Vector2
- get() = Vector2(x, y)
- set(value) {
- x = value.x
- y = value.y
- }
- inline var st: Vector2
- get() = Vector2(x, y)
- set(value) {
- x = value.x
- y = value.y
- }
-
- inline var rgb: Vector3
- get() = Vector3(x, y, z)
- set(value) {
- x = value.x
- y = value.y
- z = value.z
- }
- inline var xyz: Vector3
- get() = Vector3(x, y, z)
- set(value) {
- x = value.x
- y = value.y
- z = value.z
- }
- inline var stp: Vector3
- get() = Vector3(x, y, z)
- set(value) {
- x = value.x
- y = value.y
- z = value.z
- }
-
- inline var rgba: Vector4
- get() = Vector4(x, y, z, w)
- set(value) {
- x = value.x
- y = value.y
- z = value.z
- w = value.w
- }
- inline var xyzw: Vector4
- get() = Vector4(x, y, z, w)
- set(value) {
- x = value.x
- y = value.y
- z = value.z
- w = value.w
- }
- inline var stpq: Vector4
- get() = Vector4(x, y, z, w)
- set(value) {
- x = value.x
- y = value.y
- z = value.z
- w = value.w
- }
-
- operator fun get(index: VectorComponent) = when (index) {
- VectorComponent.X, VectorComponent.R, VectorComponent.S -> x
- VectorComponent.Y, VectorComponent.G, VectorComponent.T -> y
- VectorComponent.Z, VectorComponent.B, VectorComponent.P -> z
- VectorComponent.W, VectorComponent.A, VectorComponent.Q -> w
- }
-
- operator fun get(index1: VectorComponent, index2: VectorComponent): Vector2 {
- return Vector2(get(index1), get(index2))
- }
- operator fun get(
- index1: VectorComponent,
- index2: VectorComponent,
- index3: VectorComponent
- ): Vector3 {
- return Vector3(get(index1), get(index2), get(index3))
- }
- operator fun get(
- index1: VectorComponent,
- index2: VectorComponent,
- index3: VectorComponent,
- index4: VectorComponent
- ): Vector4 {
- return Vector4(get(index1), get(index2), get(index3), get(index4))
- }
-
- operator fun get(index: Int) = when (index) {
- 0 -> x
- 1 -> y
- 2 -> z
- 3 -> w
- else -> throw IllegalArgumentException("index must be in 0..3")
- }
-
- operator fun get(index1: Int, index2: Int) = Vector2(get(index1), get(index2))
- operator fun get(index1: Int, index2: Int, index3: Int): Vector3 {
- return Vector3(get(index1), get(index2), get(index3))
- }
- operator fun get(index1: Int, index2: Int, index3: Int, index4: Int): Vector4 {
- return Vector4(get(index1), get(index2), get(index3), get(index4))
- }
-
- operator fun set(index: Int, v: Float) = when (index) {
- 0 -> x = v
- 1 -> y = v
- 2 -> z = v
- 3 -> w = v
- else -> throw IllegalArgumentException("index must be in 0..3")
- }
-
- operator fun set(index1: Int, index2: Int, v: Float) {
- set(index1, v)
- set(index2, v)
- }
-
- operator fun set(index1: Int, index2: Int, index3: Int, v: Float) {
- set(index1, v)
- set(index2, v)
- set(index3, v)
- }
-
- operator fun set(index1: Int, index2: Int, index3: Int, index4: Int, v: Float) {
- set(index1, v)
- set(index2, v)
- set(index3, v)
- set(index4, v)
- }
-
- operator fun set(index: VectorComponent, v: Float) = when (index) {
- VectorComponent.X, VectorComponent.R, VectorComponent.S -> x = v
- VectorComponent.Y, VectorComponent.G, VectorComponent.T -> y = v
- VectorComponent.Z, VectorComponent.B, VectorComponent.P -> z = v
- VectorComponent.W, VectorComponent.A, VectorComponent.Q -> w = v
- }
-
- operator fun set(index1: VectorComponent, index2: VectorComponent, v: Float) {
- set(index1, v)
- set(index2, v)
- }
-
- operator fun set(
- index1: VectorComponent,
- index2: VectorComponent,
- index3: VectorComponent,
- v: Float
- ) {
- set(index1, v)
- set(index2, v)
- set(index3, v)
- }
-
- operator fun set(
- index1: VectorComponent,
- index2: VectorComponent,
- index3: VectorComponent,
- index4: VectorComponent,
- v: Float
- ) {
- set(index1, v)
- set(index2, v)
- set(index3, v)
- set(index4, v)
- }
-
- operator fun unaryMinus() = Vector4(-x, -y, -z, -w)
- operator fun inc() = Vector4(this).apply {
- ++x
- ++y
- ++z
- ++w
- }
- operator fun dec() = Vector4(this).apply {
- --x
- --y
- --z
- --w
- }
-
- inline operator fun plus(v: Float) = Vector4(x + v, y + v, z + v, w + v)
- inline operator fun minus(v: Float) = Vector4(x - v, y - v, z - v, w - v)
- inline operator fun times(v: Float) = Vector4(x * v, y * v, z * v, w * v)
- inline operator fun div(v: Float) = Vector4(x / v, y / v, z / v, w / v)
-
- inline operator fun plus(v: Vector2) = Vector4(x + v.x, y + v.y, z, w)
- inline operator fun minus(v: Vector2) = Vector4(x - v.x, y - v.y, z, w)
- inline operator fun times(v: Vector2) = Vector4(x * v.x, y * v.y, z, w)
- inline operator fun div(v: Vector2) = Vector4(x / v.x, y / v.y, z, w)
-
- inline operator fun plus(v: Vector3) = Vector4(x + v.x, y + v.y, z + v.z, w)
- inline operator fun minus(v: Vector3) = Vector4(x - v.x, y - v.y, z - v.z, w)
- inline operator fun times(v: Vector3) = Vector4(x * v.x, y * v.y, z * v.z, w)
- inline operator fun div(v: Vector3) = Vector4(x / v.x, y / v.y, z / v.z, w)
-
- inline operator fun plus(v: Vector4) = Vector4(x + v.x, y + v.y, z + v.z, w + v.w)
- inline operator fun minus(v: Vector4) = Vector4(x - v.x, y - v.y, z - v.z, w - v.w)
- inline operator fun times(v: Vector4) = Vector4(x * v.x, y * v.y, z * v.z, w * v.w)
- inline operator fun div(v: Vector4) = Vector4(x / v.x, y / v.y, z / v.z, w / v.w)
-
- inline fun transform(block: (Float) -> Float): Vector4 {
- x = block(x)
- y = block(y)
- z = block(z)
- w = block(w)
- return this
- }
-
- fun assignFromStorage(storage: List<Float>) {
- check(storage.size >= 4)
- x = storage[0]
- y = storage[1]
- z = storage[2]
- w = storage[3]
- }
-
- override fun toString() = "$x,$y,$z,$w"
-}
diff --git a/compose/ui/ui-graphics/src/commonMain/kotlin/androidx/compose/ui/graphics/vectormath/VectorComponent.kt b/compose/ui/ui-graphics/src/commonMain/kotlin/androidx/compose/ui/graphics/vectormath/VectorComponent.kt
deleted file mode 100644
index ab4f1a8..0000000
--- a/compose/ui/ui-graphics/src/commonMain/kotlin/androidx/compose/ui/graphics/vectormath/VectorComponent.kt
+++ /dev/null
@@ -1,22 +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.compose.ui.graphics.vectormath
-
-enum class VectorComponent {
- X, Y, Z, W,
- R, G, B, A,
- S, T, P, Q
-}
diff --git a/compose/ui/ui-graphics/src/commonMain/kotlin/androidx/compose/ui/graphics/vectormath/VectorExtensions.kt b/compose/ui/ui-graphics/src/commonMain/kotlin/androidx/compose/ui/graphics/vectormath/VectorExtensions.kt
deleted file mode 100644
index edd6e9d..0000000
--- a/compose/ui/ui-graphics/src/commonMain/kotlin/androidx/compose/ui/graphics/vectormath/VectorExtensions.kt
+++ /dev/null
@@ -1,163 +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.
- */
-@file:Suppress("NOTHING_TO_INLINE")
-package androidx.compose.ui.graphics.vectormath
-
-import kotlin.math.abs
-import kotlin.math.max
-import kotlin.math.min
-import kotlin.math.sqrt
-
-inline operator fun Float.plus(v: Vector2) = Vector2(this + v.x, this + v.y)
-inline operator fun Float.minus(v: Vector2) = Vector2(this - v.x, this - v.y)
-inline operator fun Float.times(v: Vector2) = Vector2(this * v.x, this * v.y)
-inline operator fun Float.div(v: Vector2) = Vector2(this / v.x, this / v.y)
-
-inline fun abs(v: Vector2) = Vector2(abs(v.x), abs(v.y))
-inline fun length(v: Vector2) = sqrt(v.x * v.x + v.y * v.y)
-inline fun length2(v: Vector2) = v.x * v.x + v.y * v.y
-inline fun distance(a: Vector2, b: Vector2) = length(a - b)
-inline fun dot(a: Vector2, b: Vector2) = a.x * b.x + a.y * b.y
-fun normalize(v: Vector2): Vector2 {
- val l = 1.0f / length(v)
- return Vector2(v.x * l, v.y * l)
-}
-
-inline fun reflect(i: Vector2, n: Vector2) = i - 2.0f * dot(n, i) * n
-fun refract(i: Vector2, n: Vector2, eta: Float): Vector2 {
- val d = dot(n, i)
- val k = 1.0f - eta * eta * (1.0f - (d * d))
- return if (k < 0.0f) Vector2(0.0f) else eta * i - (eta * d + sqrt(k)) * n
-}
-
-inline fun Vector2.coerceIn(min: Float, max: Float): Vector2 {
- return Vector2(
- x.coerceIn(min, max),
- y.coerceIn(min, max)
- )
-}
-
-inline fun Vector2.coerceIn(min: Vector2, max: Vector2): Vector2 {
- return Vector2(
- x.coerceIn(min.x, max.x),
- y.coerceIn(min.y, max.y)
- )
-}
-
-inline fun min(v: Vector2) = min(v.x, v.y)
-inline fun min(a: Vector2, b: Vector2) = Vector2(min(a.x, b.x), min(a.y, b.y))
-inline fun max(v: Vector2) = max(v.x, v.y)
-inline fun max(a: Vector2, b: Vector2) = Vector2(max(a.x, b.x), max(a.y, b.y))
-
-inline fun transform(v: Vector2, block: (Float) -> Float) = v.copy().transform(block)
-
-inline operator fun Float.plus(v: Vector3) = Vector3(this + v.x, this + v.y, this + v.z)
-inline operator fun Float.minus(v: Vector3) = Vector3(this - v.x, this - v.y, this - v.z)
-inline operator fun Float.times(v: Vector3) = Vector3(this * v.x, this * v.y, this * v.z)
-inline operator fun Float.div(v: Vector3) = Vector3(this / v.x, this / v.y, this / v.z)
-
-inline fun abs(v: Vector3) = Vector3(abs(v.x), abs(v.y), abs(v.z))
-inline fun length(v: Vector3) = sqrt(v.x * v.x + v.y * v.y + v.z * v.z)
-inline fun length2(v: Vector3) = v.x * v.x + v.y * v.y + v.z * v.z
-inline fun distance(a: Vector3, b: Vector3) = length(a - b)
-inline fun dot(a: Vector3, b: Vector3) = a.x * b.x + a.y * b.y + a.z * b.z
-inline fun cross(a: Vector3, b: Vector3): Vector3 {
- return Vector3(a.y * b.z - a.z * b.y, a.z * b.x - a.x * b.z, a.x * b.y - a.y * b.x)
-}
-inline infix fun Vector3.x(v: Vector3): Vector3 {
- return Vector3(y * v.z - z * v.y, z * v.x - x * v.z, x * v.y - y * v.x)
-}
-fun normalize(v: Vector3): Vector3 {
- val l = 1.0f / length(v)
- return Vector3(v.x * l, v.y * l, v.z * l)
-}
-
-inline fun reflect(i: Vector3, n: Vector3) = i - 2.0f * dot(n, i) * n
-fun refract(i: Vector3, n: Vector3, eta: Float): Vector3 {
- val d = dot(n, i)
- val k = 1.0f - eta * eta * (1.0f - (d * d))
- return if (k < 0.0f) Vector3(0.0f) else eta * i - (eta * d + sqrt(k)) * n
-}
-
-inline fun Vector3.coerceIn(min: Float, max: Float): Vector3 {
- return Vector3(
- x.coerceIn(min, max),
- y.coerceIn(min, max),
- z.coerceIn(min, max)
- )
-}
-
-inline fun Vector3.coerceIn(min: Vector3, max: Vector3): Vector3 {
- return Vector3(
- x.coerceIn(min.x, max.x),
- y.coerceIn(min.y, max.y),
- z.coerceIn(min.z, max.z)
- )
-}
-
-inline fun min(v: Vector3) = min(v.x, min(v.y, v.z))
-inline fun min(a: Vector3, b: Vector3) = Vector3(min(a.x, b.x), min(a.y, b.y), min(a.z, b.z))
-inline fun max(v: Vector3) = max(v.x, max(v.y, v.z))
-inline fun max(a: Vector3, b: Vector3) = Vector3(max(a.x, b.x), max(a.y, b.y), max(a.z, b.z))
-
-inline fun transform(v: Vector3, block: (Float) -> Float) = v.copy().transform(block)
-
-inline operator fun Float.plus(v: Vector4) = Vector4(this + v.x, this + v.y,
- this + v.z, this + v.w)
-inline operator fun Float.minus(v: Vector4) = Vector4(this - v.x, this - v.y,
- this - v.z, this - v.w)
-inline operator fun Float.times(v: Vector4) = Vector4(this * v.x, this * v.y,
- this * v.z, this * v.w)
-inline operator fun Float.div(v: Vector4) = Vector4(this / v.x, this / v.y, this / v.z, this / v.w)
-
-inline fun abs(v: Vector4) = Vector4(abs(v.x), abs(v.y), abs(v.z), abs(v.w))
-inline fun length(v: Vector4) = sqrt(v.x * v.x + v.y * v.y + v.z * v.z + v.w * v.w)
-inline fun length2(v: Vector4) = v.x * v.x + v.y * v.y + v.z * v.z + v.w * v.w
-inline fun distance(a: Vector4, b: Vector4) = length(a - b)
-inline fun dot(a: Vector4, b: Vector4) = a.x * b.x + a.y * b.y + a.z * b.z + a.w * b.w
-fun normalize(v: Vector4): Vector4 {
- val l = 1.0f / length(v)
- return Vector4(v.x * l, v.y * l, v.z * l, v.w * l)
-}
-
-inline fun Vector4.coerceIn(min: Float, max: Float): Vector4 {
- return Vector4(
- x.coerceIn(min, max),
- y.coerceIn(min, max),
- z.coerceIn(min, max),
- w.coerceIn(min, max)
- )
-}
-
-inline fun Vector4.coerceIn(min: Vector4, max: Vector4): Vector4 {
- return Vector4(
- x.coerceIn(min.x, max.x),
- y.coerceIn(min.y, max.y),
- z.coerceIn(min.z, max.z),
- w.coerceIn(min.w, max.w)
- )
-}
-
-inline fun min(v: Vector4) = min(v.x, min(v.y, min(v.z, v.w)))
-inline fun min(a: Vector4, b: Vector4): Vector4 {
- return Vector4(min(a.x, b.x), min(a.y, b.y), min(a.z, b.z), min(a.w, b.w))
-}
-inline fun max(v: Vector4) = max(v.x, max(v.y, max(v.z, v.w)))
-inline fun max(a: Vector4, b: Vector4): Vector4 {
- return Vector4(max(a.x, b.x), max(a.y, b.y), max(a.z, b.z), max(a.w, b.w))
-}
-
-inline fun transform(v: Vector4, block: (Float) -> Float) = v.copy().transform(block)
diff --git a/compose/ui/ui-graphics/src/desktopMain/kotlin/androidx/compose/ui/graphics/DesktopCanvas.kt b/compose/ui/ui-graphics/src/desktopMain/kotlin/androidx/compose/ui/graphics/DesktopCanvas.kt
index 9bc85d1d..8480fa7 100644
--- a/compose/ui/ui-graphics/src/desktopMain/kotlin/androidx/compose/ui/graphics/DesktopCanvas.kt
+++ b/compose/ui/ui-graphics/src/desktopMain/kotlin/androidx/compose/ui/graphics/DesktopCanvas.kt
@@ -19,8 +19,6 @@
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.geometry.Rect
import androidx.compose.ui.geometry.RoundRect
-import androidx.compose.ui.graphics.vectormath.Matrix4
-import androidx.compose.ui.graphics.vectormath.isIdentity
import androidx.compose.ui.unit.IntOffset
import androidx.compose.ui.unit.IntSize
import androidx.compose.ui.util.fastForEach
@@ -78,29 +76,29 @@
/**
* @throws IllegalStateException if an arbitrary transform is provided
*/
- override fun concat(matrix4: Matrix4) {
- if (!matrix4.isIdentity()) {
- if (matrix4[2, 0] != 0f ||
- matrix4[2, 1] != 0f ||
- matrix4[2, 0] != 0f ||
- matrix4[2, 1] != 0f ||
- matrix4[2, 2] != 1f ||
- matrix4[2, 3] != 0f ||
- matrix4[3, 2] != 0f
+ override fun concat(matrix: Matrix) {
+ if (!matrix.isIdentity()) {
+ if (matrix[2, 0] != 0f ||
+ matrix[2, 1] != 0f ||
+ matrix[2, 0] != 0f ||
+ matrix[2, 1] != 0f ||
+ matrix[2, 2] != 1f ||
+ matrix[2, 3] != 0f ||
+ matrix[3, 2] != 0f
) {
throw IllegalStateException("Desktop does not support arbitrary transforms")
}
skija.concat(
SkijaMatrix33(
- matrix4[0, 0],
- matrix4[1, 0],
- matrix4[3, 0],
- matrix4[0, 1],
- matrix4[1, 1],
- matrix4[3, 1],
- matrix4[0, 3],
- matrix4[1, 3],
- matrix4[3, 3]
+ matrix[0, 0],
+ matrix[1, 0],
+ matrix[3, 0],
+ matrix[0, 1],
+ matrix[1, 1],
+ matrix[3, 1],
+ matrix[0, 3],
+ matrix[1, 3],
+ matrix[3, 3]
)
)
}
diff --git a/compose/ui/ui-graphics/src/desktopMain/kotlin/androidx/compose/ui/graphics/DesktopPath.kt b/compose/ui/ui-graphics/src/desktopMain/kotlin/androidx/compose/ui/graphics/DesktopPath.kt
index 792952e..9c168c8 100644
--- a/compose/ui/ui-graphics/src/desktopMain/kotlin/androidx/compose/ui/graphics/DesktopPath.kt
+++ b/compose/ui/ui-graphics/src/desktopMain/kotlin/androidx/compose/ui/graphics/DesktopPath.kt
@@ -19,7 +19,6 @@
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.geometry.Rect
import androidx.compose.ui.geometry.RoundRect
-import androidx.compose.ui.graphics.vectormath.degrees
import org.jetbrains.skija.Matrix33
import org.jetbrains.skija.PathDirection
import org.jetbrains.skija.PathEffect
diff --git a/compose/ui/ui-graphics/src/test/java/androidx/compose/ui/graphics/MatrixTest.kt b/compose/ui/ui-graphics/src/test/java/androidx/compose/ui/graphics/MatrixTest.kt
new file mode 100644
index 0000000..29c3503
--- /dev/null
+++ b/compose/ui/ui-graphics/src/test/java/androidx/compose/ui/graphics/MatrixTest.kt
@@ -0,0 +1,250 @@
+/*
+ * Copyright 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.compose.ui.graphics
+
+import androidx.compose.ui.geometry.MutableRect
+import androidx.compose.ui.geometry.Offset
+import androidx.compose.ui.geometry.Rect
+import androidx.test.filters.SmallTest
+import org.junit.Assert.assertEquals
+import org.junit.Assert.assertFalse
+import org.junit.Assert.assertTrue
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+import kotlin.math.abs
+import kotlin.math.sqrt
+
+@SmallTest
+@RunWith(JUnit4::class)
+class MatrixTest {
+ @Test
+ fun identity() {
+ val matrix = Matrix()
+ assertTrue(matrix.isIdentity())
+ for (row in 0..3) {
+ for (column in 0..3) {
+ val expectedVal = if (row == column) 1f else 0f
+ val newVal = expectedVal + 0.1f
+
+ assertEquals(expectedVal, matrix[row, column], 0f)
+ matrix[row, column] = newVal
+ assertFalse(matrix.isIdentity())
+ matrix[row, column] = expectedVal
+ }
+ }
+ }
+
+ @Test
+ fun reset() {
+ val matrix = Matrix()
+ matrix.translate(5f)
+ matrix.reset()
+ assertTrue(matrix.isIdentity())
+ }
+
+ @Test
+ fun mapRect() {
+ val matrix = Matrix()
+
+ assertEquals(Rect(0f, 0f, 10f, 10f), matrix.map(Rect(0f, 0f, 10f, 10f)))
+
+ matrix.rotateZ(90f)
+ matrix.scale(2f, 2f)
+
+ val rect90 = matrix.map(Rect(0f, 0f, 10f, 10f))
+ assertEquals(-20f, rect90.left, 0.0001f)
+ assertEquals(0f, rect90.top, 0.0001f)
+ assertEquals(0f, rect90.right, 0.0001f)
+ assertEquals(20f, rect90.bottom, 0.0001f)
+
+ matrix.reset()
+ matrix.rotateZ(45f)
+
+ val rect45 = matrix.map(Rect(0f, 0f, 10f, 10f))
+ val sqrt2Times10 = 10f * sqrt(2f)
+ assertEquals(-sqrt2Times10 / 2f, rect45.left, 0.0001f)
+ assertEquals(0f, rect45.top, 0.0001f)
+ assertEquals(sqrt2Times10 / 2f, rect45.right, 0.0001f)
+ assertEquals(sqrt2Times10, rect45.bottom, 0.0001f)
+ }
+
+ @Test
+ fun mapMutableRect() {
+ val matrix = Matrix()
+
+ val rect = MutableRect(0f, 0f, 10f, 10f)
+ matrix.map(rect)
+ assertEquals(MutableRect(0f, 0f, 10f, 10f), rect)
+
+ matrix.rotateZ(90f)
+ matrix.scale(2f, 2f)
+
+ matrix.map(rect)
+ assertEquals(MutableRect(-20f, 0f, 0f, 20f), rect)
+
+ matrix.reset()
+ matrix.rotateZ(45f)
+
+ rect.set(0f, 0f, 10f, 10f)
+ matrix.map(rect)
+ val sqrt2Times10 = 10f * sqrt(2f)
+ assertEquals(MutableRect(-sqrt2Times10 / 2f, 0f, sqrt2Times10 / 2f, sqrt2Times10), rect)
+ }
+
+ @Test
+ fun rotateZ() {
+ val matrix = Matrix()
+
+ // no rotation
+ assertEquals(Offset(0f, 0f), matrix.map(Offset(0f, 0f)))
+ assertEquals(Offset(10f, 10f), matrix.map(Offset(10f, 10f)))
+
+ matrix.rotateZ(90f)
+ assertEquals(Offset(0f, 0f), matrix.map(Offset(0f, 0f)))
+ assertEquals(Offset(0f, 10f), matrix.map(Offset(10f, 0f)))
+ assertEquals(Offset(-10f, 10f), matrix.map(Offset(10f, 10f)))
+ }
+
+ @Test
+ fun rotateX() {
+ val matrix = Matrix()
+
+ matrix.rotateX(90f)
+ assertEquals(Offset(0f, 0f), matrix.map(Offset(0f, 0f)))
+ assertEquals(Offset(10f, 0f), matrix.map(Offset(10f, 0f)))
+ assertEquals(Offset(0f, 0f), matrix.map(Offset(0f, 10f)))
+ assertEquals(Offset(10f, 0f), matrix.map(Offset(10f, 10f)))
+ }
+
+ @Test
+ fun rotateY() {
+ val matrix = Matrix()
+
+ matrix.rotateY(90f)
+ assertEquals(Offset(0f, 0f), matrix.map(Offset(0f, 0f)))
+ assertEquals(Offset(0f, 0f), matrix.map(Offset(10f, 0f)))
+ assertEquals(Offset(0f, 10f), matrix.map(Offset(0f, 10f)))
+ assertEquals(Offset(0f, 10f), matrix.map(Offset(10f, 10f)))
+ }
+
+ @Test
+ fun scale() {
+ val matrix = Matrix()
+
+ matrix.scale(2f, 3f, 4f)
+ assertEquals(Offset(0f, 0f), matrix.map(Offset(0f, 0f)))
+ assertEquals(Offset(0f, 30f), matrix.map(Offset(0f, 10f)))
+ assertEquals(Offset(20f, 0f), matrix.map(Offset(10f, 0f)))
+ assertEquals(Offset(20f, 30f), matrix.map(Offset(10f, 10f)))
+ }
+
+ @Test
+ fun translate() {
+ val matrix = Matrix()
+
+ matrix.translate(5f, 20f, 30f)
+ assertEquals(Offset(5f, 20f), matrix.map(Offset(0f, 0f)))
+ assertEquals(Offset(5f, 30f), matrix.map(Offset(0f, 10f)))
+ assertEquals(Offset(15f, 20f), matrix.map(Offset(10f, 0f)))
+ assertEquals(Offset(15f, 30f), matrix.map(Offset(10f, 10f)))
+ }
+
+ @Test
+ fun invert() {
+ val m1 = Matrix()
+ m1.invert()
+ assertTrue(m1.isIdentity())
+
+ m1.rotateZ(12f)
+ val m2 = Matrix()
+ m2.setFrom(m1)
+ m2.invert()
+ m2 *= m1
+ assertTrue(m2.isNearIdentity())
+
+ m1.reset()
+ m1.translate(1f, 200f, 3f)
+ m1.rotateZ(30f)
+ m1.rotateX(18f)
+ m1.scale(1.2f, 1.5f, 1.1f)
+ m1.rotateY(1f)
+ m2.setFrom(m1)
+ m2.invert()
+ m2 *= m1
+ assertTrue(m2.isNearIdentity())
+ }
+
+ @Test
+ fun combineScaleTranslate() {
+ val matrix = Matrix()
+ matrix.scale(2f, 3f)
+ matrix.translate(10f, 10f)
+
+ assertEquals(Offset(20f, 30f), matrix.map(Offset(0f, 0f)))
+
+ matrix.reset()
+ matrix.translate(10f, 10f)
+ matrix.scale(2f, 3f)
+
+ assertEquals(Offset(10f, 10f), matrix.map(Offset(0f, 0f)))
+ assertEquals(Offset(30f, 40f), matrix.map(Offset(10f, 10f)))
+ }
+
+ @Test
+ fun combineRotateTranslate() {
+ val matrix = Matrix()
+ matrix.rotateZ(90f)
+ matrix.translate(10f, 10f)
+
+ assertEquals(Offset(-10f, 10f), matrix.map(Offset(0f, 0f)))
+
+ matrix.reset()
+ matrix.translate(10f, 10f)
+ matrix.rotateZ(90f)
+
+ assertEquals(Offset(10f, 10f), matrix.map(Offset(0f, 0f)))
+ assertEquals(Offset(3f, 17f), matrix.map(Offset(7f, 7f)))
+ }
+
+ companion object {
+ private fun assertEquals(expected: Offset, actual: Offset) {
+ assertEquals(expected.x, actual.x, 0.0001f)
+ assertEquals(expected.y, actual.y, 0.0001f)
+ }
+
+ private fun assertEquals(expected: MutableRect, actual: MutableRect) {
+ assertEquals(expected.left, actual.left, 0.0001f)
+ assertEquals(expected.top, actual.top, 0.0001f)
+ assertEquals(expected.right, actual.right, 0.0001f)
+ assertEquals(expected.bottom, actual.bottom, 0.0001f)
+ }
+
+ private fun Matrix.isNearIdentity(): Boolean {
+ for (row in 0..3) {
+ for (column in 0..3) {
+ val expected = if (row == column) 1f else 0f
+ val delta = this[row, column] - expected
+ if (abs(delta) > 0.0001f) {
+ return false
+ }
+ }
+ }
+ return true
+ }
+ }
+}
\ No newline at end of file
diff --git a/compose/ui/ui-test-font/build.gradle b/compose/ui/ui-test-font/build.gradle
index 66db19f..6fdc8fb 100644
--- a/compose/ui/ui-test-font/build.gradle
+++ b/compose/ui/ui-test-font/build.gradle
@@ -27,7 +27,7 @@
androidx {
name = "Compose Test Font resources"
publish = Publish.NONE
- mavenVersion = LibraryVersions.UI
+ mavenVersion = LibraryVersions.COMPOSE
mavenGroup = LibraryGroups.Compose.UI
inceptionYear = "2020"
description = "Fonts designed for testing text capabilities"
diff --git a/compose/ui/ui-text-android/build.gradle b/compose/ui/ui-text-android/build.gradle
index 8ae134e..37a2fcd 100644
--- a/compose/ui/ui-text-android/build.gradle
+++ b/compose/ui/ui-text-android/build.gradle
@@ -54,7 +54,7 @@
name = "Compose UI Text Android support"
publish = Publish.SNAPSHOT_AND_RELEASE
generateDocs = false
- mavenVersion = LibraryVersions.UI
+ mavenVersion = LibraryVersions.COMPOSE
mavenGroup = LibraryGroups.Compose.UI
inceptionYear = "2018"
description = "Android specific implementations for Text in Compose"
diff --git a/compose/ui/ui-text/build.gradle b/compose/ui/ui-text/build.gradle
index e6e5c14..4f77808 100644
--- a/compose/ui/ui-text/build.gradle
+++ b/compose/ui/ui-text/build.gradle
@@ -94,7 +94,7 @@
androidx {
name = "Compose UI Text"
publish = Publish.SNAPSHOT_AND_RELEASE
- mavenVersion = LibraryVersions.UI
+ mavenVersion = LibraryVersions.COMPOSE
mavenGroup = LibraryGroups.Compose.UI
inceptionYear = "2019"
description = "Compose Text primitives and utilities"
diff --git a/compose/ui/ui-unit/build.gradle b/compose/ui/ui-unit/build.gradle
index 6ad2f711..d837e87 100644
--- a/compose/ui/ui-unit/build.gradle
+++ b/compose/ui/ui-unit/build.gradle
@@ -65,7 +65,7 @@
androidx {
name = "Compose Unit"
publish = Publish.SNAPSHOT_AND_RELEASE
- mavenVersion = LibraryVersions.UI
+ mavenVersion = LibraryVersions.COMPOSE
mavenGroup = LibraryGroups.Compose.UI
inceptionYear = "2020"
description = "Compose classes for simple units"
diff --git a/compose/ui/ui-util/build.gradle b/compose/ui/ui-util/build.gradle
index 9d6b9d5..455308c 100644
--- a/compose/ui/ui-util/build.gradle
+++ b/compose/ui/ui-util/build.gradle
@@ -62,7 +62,7 @@
androidx {
name = "Compose Util"
publish = Publish.SNAPSHOT_AND_RELEASE
- mavenVersion = LibraryVersions.UI
+ mavenVersion = LibraryVersions.COMPOSE
mavenGroup = LibraryGroups.Compose.UI
inceptionYear = "2020"
description = "Internal Compose utilities used by other modules"
diff --git a/compose/ui/ui-viewbinding/build.gradle b/compose/ui/ui-viewbinding/build.gradle
index a74564c..940c8fe 100644
--- a/compose/ui/ui-viewbinding/build.gradle
+++ b/compose/ui/ui-viewbinding/build.gradle
@@ -44,7 +44,7 @@
androidx {
name = "Compose ViewBinding"
publish = Publish.SNAPSHOT_AND_RELEASE
- mavenVersion = LibraryVersions.UI
+ mavenVersion = LibraryVersions.COMPOSE
mavenGroup = LibraryGroups.Compose.UI
inceptionYear = "2020"
description = "Compose integration with ViewBinding"
diff --git a/compose/ui/ui/api/api_lint.ignore b/compose/ui/ui/api/api_lint.ignore
index 913efd5..498ccdb 100644
--- a/compose/ui/ui/api/api_lint.ignore
+++ b/compose/ui/ui/api/api_lint.ignore
@@ -17,6 +17,10 @@
Builder methods names should use setFoo() / addFoo() / clearFoo() style: method androidx.compose.ui.graphics.vector.VectorAssetBuilder.pushGroup(String,float,float,float,float,float,float,float,java.util.List<? extends androidx.compose.ui.graphics.vector.PathNode>)
+CallbackMethodName: androidx.compose.ui.gesture.ScrollCallback#onStart-k-4lQ0M(long):
+ Callback method names must follow the on<Something> style: onStart-k-4lQ0M
+
+
CallbackName: androidx.compose.ui.gesture.DragObserver:
Class should be named DragCallback
CallbackName: androidx.compose.ui.gesture.LongPressDragObserver:
diff --git a/compose/ui/ui/api/current.txt b/compose/ui/ui/api/current.txt
index 0a65062..5c28f5b 100644
--- a/compose/ui/ui/api/current.txt
+++ b/compose/ui/ui/api/current.txt
@@ -721,7 +721,7 @@
}
public final class VectorAssetBuilder {
- method public androidx.compose.ui.graphics.vector.VectorAssetBuilder addPath(java.util.List<? extends androidx.compose.ui.graphics.vector.PathNode> pathData, String name = "", androidx.compose.ui.graphics.Brush? fill = null, float fillAlpha = 1.0f, androidx.compose.ui.graphics.Brush? stroke = null, float strokeAlpha = 1.0f, float strokeLineWidth = 0.0f, androidx.compose.ui.graphics.StrokeCap strokeLineCap = DefaultStrokeLineCap, androidx.compose.ui.graphics.StrokeJoin strokeLineJoin = DefaultStrokeLineJoin, float strokeLineMiter = 4.0f, float trimPathStart = 0.0f, float trimPathEnd = 1.0f, float trimPathOffset = 0.0f);
+ method public androidx.compose.ui.graphics.vector.VectorAssetBuilder addPath(java.util.List<? extends androidx.compose.ui.graphics.vector.PathNode> pathData, androidx.compose.ui.graphics.PathFillType pathFillType = DefaultFillType, String name = "", androidx.compose.ui.graphics.Brush? fill = null, float fillAlpha = 1.0f, androidx.compose.ui.graphics.Brush? stroke = null, float strokeAlpha = 1.0f, float strokeLineWidth = 0.0f, androidx.compose.ui.graphics.StrokeCap strokeLineCap = DefaultStrokeLineCap, androidx.compose.ui.graphics.StrokeJoin strokeLineJoin = DefaultStrokeLineJoin, float strokeLineMiter = 4.0f, float trimPathStart = 0.0f, float trimPathEnd = 1.0f, float trimPathOffset = 0.0f);
method public androidx.compose.ui.graphics.vector.VectorAsset build();
method public float getDefaultHeight();
method public float getDefaultWidth();
@@ -734,12 +734,12 @@
public final class VectorAssetBuilderKt {
method public static inline androidx.compose.ui.graphics.vector.VectorAssetBuilder group(androidx.compose.ui.graphics.vector.VectorAssetBuilder, String name = "", float rotate = 0.0f, float pivotX = 0.0f, float pivotY = 0.0f, float scaleX = 1.0f, float scaleY = 1.0f, float translationX = 0.0f, float translationY = 0.0f, java.util.List<? extends androidx.compose.ui.graphics.vector.PathNode> clipPathData = EmptyPath, kotlin.jvm.functions.Function1<? super androidx.compose.ui.graphics.vector.VectorAssetBuilder,kotlin.Unit> block);
- method public static inline androidx.compose.ui.graphics.vector.VectorAssetBuilder path(androidx.compose.ui.graphics.vector.VectorAssetBuilder, String name = "", androidx.compose.ui.graphics.Brush? fill = null, float fillAlpha = 1.0f, androidx.compose.ui.graphics.Brush? stroke = null, float strokeAlpha = 1.0f, float strokeLineWidth = 0.0f, androidx.compose.ui.graphics.StrokeCap strokeLineCap = DefaultStrokeLineCap, androidx.compose.ui.graphics.StrokeJoin strokeLineJoin = DefaultStrokeLineJoin, float strokeLineMiter = 4.0f, kotlin.jvm.functions.Function1<? super androidx.compose.ui.graphics.vector.PathBuilder,kotlin.Unit> pathBuilder);
+ method public static inline androidx.compose.ui.graphics.vector.VectorAssetBuilder path(androidx.compose.ui.graphics.vector.VectorAssetBuilder, String name = "", androidx.compose.ui.graphics.Brush? fill = null, float fillAlpha = 1.0f, androidx.compose.ui.graphics.Brush? stroke = null, float strokeAlpha = 1.0f, float strokeLineWidth = 0.0f, androidx.compose.ui.graphics.StrokeCap strokeLineCap = DefaultStrokeLineCap, androidx.compose.ui.graphics.StrokeJoin strokeLineJoin = DefaultStrokeLineJoin, float strokeLineMiter = 4.0f, androidx.compose.ui.graphics.PathFillType pathFillType = DefaultFillType, kotlin.jvm.functions.Function1<? super androidx.compose.ui.graphics.vector.PathBuilder,kotlin.Unit> pathBuilder);
}
public final class VectorComposeKt {
method @androidx.compose.runtime.Composable public static void Group(String name = "", float rotation = 0.0f, float pivotX = 0.0f, float pivotY = 0.0f, float scaleX = 1.0f, float scaleY = 1.0f, float translationX = 0.0f, float translationY = 0.0f, java.util.List<? extends androidx.compose.ui.graphics.vector.PathNode> clipPathData = EmptyPath, kotlin.jvm.functions.Function0<kotlin.Unit> children);
- method @androidx.compose.runtime.Composable public static void Path(java.util.List<? extends androidx.compose.ui.graphics.vector.PathNode> pathData, String name = "", androidx.compose.ui.graphics.Brush? fill = null, float fillAlpha = 1.0f, androidx.compose.ui.graphics.Brush? stroke = null, float strokeAlpha = 1.0f, float strokeLineWidth = 0.0f, androidx.compose.ui.graphics.StrokeCap strokeLineCap = DefaultStrokeLineCap, androidx.compose.ui.graphics.StrokeJoin strokeLineJoin = DefaultStrokeLineJoin, float strokeLineMiter = 4.0f, float trimPathStart = 0.0f, float trimPathEnd = 1.0f, float trimPathOffset = 0.0f);
+ method @androidx.compose.runtime.Composable public static void Path(java.util.List<? extends androidx.compose.ui.graphics.vector.PathNode> pathData, androidx.compose.ui.graphics.PathFillType pathFillType = DefaultFillType, String name = "", androidx.compose.ui.graphics.Brush? fill = null, float fillAlpha = 1.0f, androidx.compose.ui.graphics.Brush? stroke = null, float strokeAlpha = 1.0f, float strokeLineWidth = 0.0f, androidx.compose.ui.graphics.StrokeCap strokeLineCap = DefaultStrokeLineCap, androidx.compose.ui.graphics.StrokeJoin strokeLineJoin = DefaultStrokeLineJoin, float strokeLineMiter = 4.0f, float trimPathStart = 0.0f, float trimPathEnd = 1.0f, float trimPathOffset = 0.0f);
}
@androidx.compose.runtime.Immutable public final class VectorGroup extends androidx.compose.ui.graphics.vector.VectorNode implements java.lang.Iterable<androidx.compose.ui.graphics.vector.VectorNode> kotlin.jvm.internal.markers.KMappedMarker {
@@ -761,6 +761,7 @@
public final class VectorKt {
method public static inline java.util.List<androidx.compose.ui.graphics.vector.PathNode> PathData(kotlin.jvm.functions.Function1<? super androidx.compose.ui.graphics.vector.PathBuilder,kotlin.Unit> block);
method public static java.util.List<androidx.compose.ui.graphics.vector.PathNode> addPathNodes(String? pathStr);
+ method public static androidx.compose.ui.graphics.PathFillType getDefaultFillType();
method public static androidx.compose.ui.graphics.StrokeCap getDefaultStrokeLineCap();
method public static androidx.compose.ui.graphics.StrokeJoin getDefaultStrokeLineJoin();
method public static androidx.compose.ui.graphics.BlendMode getDefaultTintBlendMode();
@@ -802,6 +803,7 @@
method public float getFillAlpha();
method public String getName();
method public java.util.List<androidx.compose.ui.graphics.vector.PathNode> getPathData();
+ method public androidx.compose.ui.graphics.PathFillType getPathFillType();
method public androidx.compose.ui.graphics.Brush? getStroke();
method public float getStrokeAlpha();
method public androidx.compose.ui.graphics.StrokeCap getStrokeLineCap();
@@ -1812,7 +1814,7 @@
method public void destroy();
method public void drawLayer(androidx.compose.ui.graphics.Canvas canvas);
method public long getLayerId();
- method public android.graphics.Matrix getMatrix();
+ method public void getMatrix-58bKbWc(float[] matrix);
method public androidx.compose.ui.DrawLayerModifier getModifier();
method public void invalidate();
method public void move--gyyYBs(long position);
@@ -1926,9 +1928,6 @@
method public static androidx.compose.runtime.ProvidableAmbient<androidx.compose.ui.platform.UriHandler> getUriHandlerAmbient();
}
- public final class AndroidActualsKt {
- }
-
public final class AndroidAmbientsKt {
method public static androidx.compose.runtime.ProvidableAmbient<android.content.res.Configuration> getConfigurationAmbient();
method public static androidx.compose.runtime.ProvidableAmbient<android.content.Context> getContextAmbient();
diff --git a/compose/ui/ui/api/public_plus_experimental_current.txt b/compose/ui/ui/api/public_plus_experimental_current.txt
index 0a65062..5c28f5b 100644
--- a/compose/ui/ui/api/public_plus_experimental_current.txt
+++ b/compose/ui/ui/api/public_plus_experimental_current.txt
@@ -721,7 +721,7 @@
}
public final class VectorAssetBuilder {
- method public androidx.compose.ui.graphics.vector.VectorAssetBuilder addPath(java.util.List<? extends androidx.compose.ui.graphics.vector.PathNode> pathData, String name = "", androidx.compose.ui.graphics.Brush? fill = null, float fillAlpha = 1.0f, androidx.compose.ui.graphics.Brush? stroke = null, float strokeAlpha = 1.0f, float strokeLineWidth = 0.0f, androidx.compose.ui.graphics.StrokeCap strokeLineCap = DefaultStrokeLineCap, androidx.compose.ui.graphics.StrokeJoin strokeLineJoin = DefaultStrokeLineJoin, float strokeLineMiter = 4.0f, float trimPathStart = 0.0f, float trimPathEnd = 1.0f, float trimPathOffset = 0.0f);
+ method public androidx.compose.ui.graphics.vector.VectorAssetBuilder addPath(java.util.List<? extends androidx.compose.ui.graphics.vector.PathNode> pathData, androidx.compose.ui.graphics.PathFillType pathFillType = DefaultFillType, String name = "", androidx.compose.ui.graphics.Brush? fill = null, float fillAlpha = 1.0f, androidx.compose.ui.graphics.Brush? stroke = null, float strokeAlpha = 1.0f, float strokeLineWidth = 0.0f, androidx.compose.ui.graphics.StrokeCap strokeLineCap = DefaultStrokeLineCap, androidx.compose.ui.graphics.StrokeJoin strokeLineJoin = DefaultStrokeLineJoin, float strokeLineMiter = 4.0f, float trimPathStart = 0.0f, float trimPathEnd = 1.0f, float trimPathOffset = 0.0f);
method public androidx.compose.ui.graphics.vector.VectorAsset build();
method public float getDefaultHeight();
method public float getDefaultWidth();
@@ -734,12 +734,12 @@
public final class VectorAssetBuilderKt {
method public static inline androidx.compose.ui.graphics.vector.VectorAssetBuilder group(androidx.compose.ui.graphics.vector.VectorAssetBuilder, String name = "", float rotate = 0.0f, float pivotX = 0.0f, float pivotY = 0.0f, float scaleX = 1.0f, float scaleY = 1.0f, float translationX = 0.0f, float translationY = 0.0f, java.util.List<? extends androidx.compose.ui.graphics.vector.PathNode> clipPathData = EmptyPath, kotlin.jvm.functions.Function1<? super androidx.compose.ui.graphics.vector.VectorAssetBuilder,kotlin.Unit> block);
- method public static inline androidx.compose.ui.graphics.vector.VectorAssetBuilder path(androidx.compose.ui.graphics.vector.VectorAssetBuilder, String name = "", androidx.compose.ui.graphics.Brush? fill = null, float fillAlpha = 1.0f, androidx.compose.ui.graphics.Brush? stroke = null, float strokeAlpha = 1.0f, float strokeLineWidth = 0.0f, androidx.compose.ui.graphics.StrokeCap strokeLineCap = DefaultStrokeLineCap, androidx.compose.ui.graphics.StrokeJoin strokeLineJoin = DefaultStrokeLineJoin, float strokeLineMiter = 4.0f, kotlin.jvm.functions.Function1<? super androidx.compose.ui.graphics.vector.PathBuilder,kotlin.Unit> pathBuilder);
+ method public static inline androidx.compose.ui.graphics.vector.VectorAssetBuilder path(androidx.compose.ui.graphics.vector.VectorAssetBuilder, String name = "", androidx.compose.ui.graphics.Brush? fill = null, float fillAlpha = 1.0f, androidx.compose.ui.graphics.Brush? stroke = null, float strokeAlpha = 1.0f, float strokeLineWidth = 0.0f, androidx.compose.ui.graphics.StrokeCap strokeLineCap = DefaultStrokeLineCap, androidx.compose.ui.graphics.StrokeJoin strokeLineJoin = DefaultStrokeLineJoin, float strokeLineMiter = 4.0f, androidx.compose.ui.graphics.PathFillType pathFillType = DefaultFillType, kotlin.jvm.functions.Function1<? super androidx.compose.ui.graphics.vector.PathBuilder,kotlin.Unit> pathBuilder);
}
public final class VectorComposeKt {
method @androidx.compose.runtime.Composable public static void Group(String name = "", float rotation = 0.0f, float pivotX = 0.0f, float pivotY = 0.0f, float scaleX = 1.0f, float scaleY = 1.0f, float translationX = 0.0f, float translationY = 0.0f, java.util.List<? extends androidx.compose.ui.graphics.vector.PathNode> clipPathData = EmptyPath, kotlin.jvm.functions.Function0<kotlin.Unit> children);
- method @androidx.compose.runtime.Composable public static void Path(java.util.List<? extends androidx.compose.ui.graphics.vector.PathNode> pathData, String name = "", androidx.compose.ui.graphics.Brush? fill = null, float fillAlpha = 1.0f, androidx.compose.ui.graphics.Brush? stroke = null, float strokeAlpha = 1.0f, float strokeLineWidth = 0.0f, androidx.compose.ui.graphics.StrokeCap strokeLineCap = DefaultStrokeLineCap, androidx.compose.ui.graphics.StrokeJoin strokeLineJoin = DefaultStrokeLineJoin, float strokeLineMiter = 4.0f, float trimPathStart = 0.0f, float trimPathEnd = 1.0f, float trimPathOffset = 0.0f);
+ method @androidx.compose.runtime.Composable public static void Path(java.util.List<? extends androidx.compose.ui.graphics.vector.PathNode> pathData, androidx.compose.ui.graphics.PathFillType pathFillType = DefaultFillType, String name = "", androidx.compose.ui.graphics.Brush? fill = null, float fillAlpha = 1.0f, androidx.compose.ui.graphics.Brush? stroke = null, float strokeAlpha = 1.0f, float strokeLineWidth = 0.0f, androidx.compose.ui.graphics.StrokeCap strokeLineCap = DefaultStrokeLineCap, androidx.compose.ui.graphics.StrokeJoin strokeLineJoin = DefaultStrokeLineJoin, float strokeLineMiter = 4.0f, float trimPathStart = 0.0f, float trimPathEnd = 1.0f, float trimPathOffset = 0.0f);
}
@androidx.compose.runtime.Immutable public final class VectorGroup extends androidx.compose.ui.graphics.vector.VectorNode implements java.lang.Iterable<androidx.compose.ui.graphics.vector.VectorNode> kotlin.jvm.internal.markers.KMappedMarker {
@@ -761,6 +761,7 @@
public final class VectorKt {
method public static inline java.util.List<androidx.compose.ui.graphics.vector.PathNode> PathData(kotlin.jvm.functions.Function1<? super androidx.compose.ui.graphics.vector.PathBuilder,kotlin.Unit> block);
method public static java.util.List<androidx.compose.ui.graphics.vector.PathNode> addPathNodes(String? pathStr);
+ method public static androidx.compose.ui.graphics.PathFillType getDefaultFillType();
method public static androidx.compose.ui.graphics.StrokeCap getDefaultStrokeLineCap();
method public static androidx.compose.ui.graphics.StrokeJoin getDefaultStrokeLineJoin();
method public static androidx.compose.ui.graphics.BlendMode getDefaultTintBlendMode();
@@ -802,6 +803,7 @@
method public float getFillAlpha();
method public String getName();
method public java.util.List<androidx.compose.ui.graphics.vector.PathNode> getPathData();
+ method public androidx.compose.ui.graphics.PathFillType getPathFillType();
method public androidx.compose.ui.graphics.Brush? getStroke();
method public float getStrokeAlpha();
method public androidx.compose.ui.graphics.StrokeCap getStrokeLineCap();
@@ -1812,7 +1814,7 @@
method public void destroy();
method public void drawLayer(androidx.compose.ui.graphics.Canvas canvas);
method public long getLayerId();
- method public android.graphics.Matrix getMatrix();
+ method public void getMatrix-58bKbWc(float[] matrix);
method public androidx.compose.ui.DrawLayerModifier getModifier();
method public void invalidate();
method public void move--gyyYBs(long position);
@@ -1926,9 +1928,6 @@
method public static androidx.compose.runtime.ProvidableAmbient<androidx.compose.ui.platform.UriHandler> getUriHandlerAmbient();
}
- public final class AndroidActualsKt {
- }
-
public final class AndroidAmbientsKt {
method public static androidx.compose.runtime.ProvidableAmbient<android.content.res.Configuration> getConfigurationAmbient();
method public static androidx.compose.runtime.ProvidableAmbient<android.content.Context> getContextAmbient();
diff --git a/compose/ui/ui/api/restricted_current.txt b/compose/ui/ui/api/restricted_current.txt
index 951a49d..55c3aae 100644
--- a/compose/ui/ui/api/restricted_current.txt
+++ b/compose/ui/ui/api/restricted_current.txt
@@ -768,7 +768,7 @@
}
public final class VectorAssetBuilder {
- method public androidx.compose.ui.graphics.vector.VectorAssetBuilder addPath(java.util.List<? extends androidx.compose.ui.graphics.vector.PathNode> pathData, String name = "", androidx.compose.ui.graphics.Brush? fill = null, float fillAlpha = 1.0f, androidx.compose.ui.graphics.Brush? stroke = null, float strokeAlpha = 1.0f, float strokeLineWidth = 0.0f, androidx.compose.ui.graphics.StrokeCap strokeLineCap = DefaultStrokeLineCap, androidx.compose.ui.graphics.StrokeJoin strokeLineJoin = DefaultStrokeLineJoin, float strokeLineMiter = 4.0f, float trimPathStart = 0.0f, float trimPathEnd = 1.0f, float trimPathOffset = 0.0f);
+ method public androidx.compose.ui.graphics.vector.VectorAssetBuilder addPath(java.util.List<? extends androidx.compose.ui.graphics.vector.PathNode> pathData, androidx.compose.ui.graphics.PathFillType pathFillType = DefaultFillType, String name = "", androidx.compose.ui.graphics.Brush? fill = null, float fillAlpha = 1.0f, androidx.compose.ui.graphics.Brush? stroke = null, float strokeAlpha = 1.0f, float strokeLineWidth = 0.0f, androidx.compose.ui.graphics.StrokeCap strokeLineCap = DefaultStrokeLineCap, androidx.compose.ui.graphics.StrokeJoin strokeLineJoin = DefaultStrokeLineJoin, float strokeLineMiter = 4.0f, float trimPathStart = 0.0f, float trimPathEnd = 1.0f, float trimPathOffset = 0.0f);
method public androidx.compose.ui.graphics.vector.VectorAsset build();
method public float getDefaultHeight();
method public float getDefaultWidth();
@@ -781,12 +781,12 @@
public final class VectorAssetBuilderKt {
method public static inline androidx.compose.ui.graphics.vector.VectorAssetBuilder group(androidx.compose.ui.graphics.vector.VectorAssetBuilder, String name = "", float rotate = 0.0f, float pivotX = 0.0f, float pivotY = 0.0f, float scaleX = 1.0f, float scaleY = 1.0f, float translationX = 0.0f, float translationY = 0.0f, java.util.List<? extends androidx.compose.ui.graphics.vector.PathNode> clipPathData = EmptyPath, kotlin.jvm.functions.Function1<? super androidx.compose.ui.graphics.vector.VectorAssetBuilder,kotlin.Unit> block);
- method public static inline androidx.compose.ui.graphics.vector.VectorAssetBuilder path(androidx.compose.ui.graphics.vector.VectorAssetBuilder, String name = "", androidx.compose.ui.graphics.Brush? fill = null, float fillAlpha = 1.0f, androidx.compose.ui.graphics.Brush? stroke = null, float strokeAlpha = 1.0f, float strokeLineWidth = 0.0f, androidx.compose.ui.graphics.StrokeCap strokeLineCap = DefaultStrokeLineCap, androidx.compose.ui.graphics.StrokeJoin strokeLineJoin = DefaultStrokeLineJoin, float strokeLineMiter = 4.0f, kotlin.jvm.functions.Function1<? super androidx.compose.ui.graphics.vector.PathBuilder,kotlin.Unit> pathBuilder);
+ method public static inline androidx.compose.ui.graphics.vector.VectorAssetBuilder path(androidx.compose.ui.graphics.vector.VectorAssetBuilder, String name = "", androidx.compose.ui.graphics.Brush? fill = null, float fillAlpha = 1.0f, androidx.compose.ui.graphics.Brush? stroke = null, float strokeAlpha = 1.0f, float strokeLineWidth = 0.0f, androidx.compose.ui.graphics.StrokeCap strokeLineCap = DefaultStrokeLineCap, androidx.compose.ui.graphics.StrokeJoin strokeLineJoin = DefaultStrokeLineJoin, float strokeLineMiter = 4.0f, androidx.compose.ui.graphics.PathFillType pathFillType = DefaultFillType, kotlin.jvm.functions.Function1<? super androidx.compose.ui.graphics.vector.PathBuilder,kotlin.Unit> pathBuilder);
}
public final class VectorComposeKt {
method @androidx.compose.runtime.Composable public static void Group(String name = "", float rotation = 0.0f, float pivotX = 0.0f, float pivotY = 0.0f, float scaleX = 1.0f, float scaleY = 1.0f, float translationX = 0.0f, float translationY = 0.0f, java.util.List<? extends androidx.compose.ui.graphics.vector.PathNode> clipPathData = EmptyPath, kotlin.jvm.functions.Function0<kotlin.Unit> children);
- method @androidx.compose.runtime.Composable public static void Path(java.util.List<? extends androidx.compose.ui.graphics.vector.PathNode> pathData, String name = "", androidx.compose.ui.graphics.Brush? fill = null, float fillAlpha = 1.0f, androidx.compose.ui.graphics.Brush? stroke = null, float strokeAlpha = 1.0f, float strokeLineWidth = 0.0f, androidx.compose.ui.graphics.StrokeCap strokeLineCap = DefaultStrokeLineCap, androidx.compose.ui.graphics.StrokeJoin strokeLineJoin = DefaultStrokeLineJoin, float strokeLineMiter = 4.0f, float trimPathStart = 0.0f, float trimPathEnd = 1.0f, float trimPathOffset = 0.0f);
+ method @androidx.compose.runtime.Composable public static void Path(java.util.List<? extends androidx.compose.ui.graphics.vector.PathNode> pathData, androidx.compose.ui.graphics.PathFillType pathFillType = DefaultFillType, String name = "", androidx.compose.ui.graphics.Brush? fill = null, float fillAlpha = 1.0f, androidx.compose.ui.graphics.Brush? stroke = null, float strokeAlpha = 1.0f, float strokeLineWidth = 0.0f, androidx.compose.ui.graphics.StrokeCap strokeLineCap = DefaultStrokeLineCap, androidx.compose.ui.graphics.StrokeJoin strokeLineJoin = DefaultStrokeLineJoin, float strokeLineMiter = 4.0f, float trimPathStart = 0.0f, float trimPathEnd = 1.0f, float trimPathOffset = 0.0f);
}
@androidx.compose.runtime.Immutable public final class VectorGroup extends androidx.compose.ui.graphics.vector.VectorNode implements java.lang.Iterable<androidx.compose.ui.graphics.vector.VectorNode> kotlin.jvm.internal.markers.KMappedMarker {
@@ -808,6 +808,7 @@
public final class VectorKt {
method public static inline java.util.List<androidx.compose.ui.graphics.vector.PathNode> PathData(kotlin.jvm.functions.Function1<? super androidx.compose.ui.graphics.vector.PathBuilder,kotlin.Unit> block);
method public static java.util.List<androidx.compose.ui.graphics.vector.PathNode> addPathNodes(String? pathStr);
+ method public static androidx.compose.ui.graphics.PathFillType getDefaultFillType();
method public static androidx.compose.ui.graphics.StrokeCap getDefaultStrokeLineCap();
method public static androidx.compose.ui.graphics.StrokeJoin getDefaultStrokeLineJoin();
method public static androidx.compose.ui.graphics.BlendMode getDefaultTintBlendMode();
@@ -849,6 +850,7 @@
method public float getFillAlpha();
method public String getName();
method public java.util.List<androidx.compose.ui.graphics.vector.PathNode> getPathData();
+ method public androidx.compose.ui.graphics.PathFillType getPathFillType();
method public androidx.compose.ui.graphics.Brush? getStroke();
method public float getStrokeAlpha();
method public androidx.compose.ui.graphics.StrokeCap getStrokeLineCap();
@@ -1872,7 +1874,7 @@
method public void destroy();
method public void drawLayer(androidx.compose.ui.graphics.Canvas canvas);
method public long getLayerId();
- method public android.graphics.Matrix getMatrix();
+ method public void getMatrix-58bKbWc(float[] matrix);
method public androidx.compose.ui.DrawLayerModifier getModifier();
method public void invalidate();
method public void move--gyyYBs(long position);
@@ -1995,9 +1997,6 @@
method public static androidx.compose.runtime.ProvidableAmbient<androidx.compose.ui.platform.UriHandler> getUriHandlerAmbient();
}
- public final class AndroidActualsKt {
- }
-
public final class AndroidAmbientsKt {
method public static androidx.compose.runtime.ProvidableAmbient<android.content.res.Configuration> getConfigurationAmbient();
method public static androidx.compose.runtime.ProvidableAmbient<android.content.Context> getContextAmbient();
diff --git a/compose/ui/ui/build.gradle b/compose/ui/ui/build.gradle
index 072f281..49a4fc7 100644
--- a/compose/ui/ui/build.gradle
+++ b/compose/ui/ui/build.gradle
@@ -122,6 +122,7 @@
exclude group: 'org.mockito' // to keep control on the mockito version
}
implementation(SKIKO_CURRENT_OS)
+ implementation project(":compose:foundation:foundation")
}
}
}
@@ -129,7 +130,7 @@
androidx {
name = "Compose UI primitives"
publish = Publish.SNAPSHOT_AND_RELEASE
- mavenVersion = LibraryVersions.UI
+ mavenVersion = LibraryVersions.COMPOSE
mavenGroup = LibraryGroups.Compose.UI
inceptionYear = "2019"
description = "Compose UI primitives. This library contains the primitives that form the Compose UI Toolkit, such as drawing, measurement and layout."
diff --git a/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/AndroidLayoutDrawTest.kt b/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/AndroidLayoutDrawTest.kt
index 6ba5fe3..2cdf816 100644
--- a/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/AndroidLayoutDrawTest.kt
+++ b/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/AndroidLayoutDrawTest.kt
@@ -2725,6 +2725,29 @@
}
}
+ // Make sure that when the child of a layer changes that the drawing changes to match.
+ @SdkSuppress(minSdkVersion = Build.VERSION_CODES.O)
+ @Test
+ fun changedLayerChild() {
+ var showInner by mutableStateOf(true)
+ activityTestRule.runOnUiThread {
+ activity.setContent {
+ FixedSize(
+ size = 10,
+ modifier = Modifier.background(Color.Blue)
+ .then(PaddingModifier(10))
+ .drawLayer()
+ .then(if (showInner) Modifier.background(Color.White) else Modifier)
+ .drawLatchModifier()
+ )
+ }
+ }
+ validateSquareColors(outerColor = Color.Blue, innerColor = Color.White, size = 10)
+ drawLatch = CountDownLatch(1)
+ showInner = false
+ validateSquareColors(outerColor = Color.Blue, innerColor = Color.Blue, size = 10)
+ }
+
private fun composeSquares(model: SquareModel) {
activityTestRule.runOnUiThreadIR {
activity.setContent {
diff --git a/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/graphics/vector/compat/XmlVectorParser.kt b/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/graphics/vector/compat/XmlVectorParser.kt
index 163a311..c1f9534 100644
--- a/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/graphics/vector/compat/XmlVectorParser.kt
+++ b/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/graphics/vector/compat/XmlVectorParser.kt
@@ -20,6 +20,7 @@
import android.util.AttributeSet
import androidx.compose.ui.graphics.Brush
import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.graphics.PathFillType
import androidx.compose.ui.graphics.ShaderBrush
import androidx.compose.ui.graphics.SolidColor
import androidx.compose.ui.graphics.StrokeCap
@@ -291,20 +292,21 @@
0.0f
)
- // TODO(njawad) handle fill rule
-// val fillRule = TypedArrayUtils.getNamedInt(
-// a, this, "fillType",
-// AndroidVectorResources.STYLEABLE_VECTOR_DRAWABLE_PATH_TRIM_PATH_FILLTYPE,
-// FILL_TYPE_WINDING
-// )
+ val fillRule = TypedArrayUtils.getNamedInt(
+ a, this, "fillType",
+ AndroidVectorResources.STYLEABLE_VECTOR_DRAWABLE_PATH_TRIM_PATH_FILLTYPE,
+ FILL_TYPE_WINDING
+ )
a.recycle()
val fillBrush = obtainBrushFromComplexColor(fillColor)
val strokeBrush = obtainBrushFromComplexColor(strokeColor)
+ val fillPathType = if (fillRule == 0) PathFillType.NonZero else PathFillType.EvenOdd
builder.addPath(
pathData,
+ fillPathType,
name,
fillBrush,
fillAlpha,
diff --git a/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/platform/AndroidActuals.kt b/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/platform/AndroidActuals.kt
deleted file mode 100644
index b75336d..0000000
--- a/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/platform/AndroidActuals.kt
+++ /dev/null
@@ -1,20 +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.compose.ui.platform
-
-internal actual typealias NativeRectF = android.graphics.RectF
-internal actual typealias NativeMatrix = android.graphics.Matrix
diff --git a/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/platform/RenderNodeLayer.kt b/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/platform/RenderNodeLayer.kt
index 7d85137..b711f730a 100644
--- a/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/platform/RenderNodeLayer.kt
+++ b/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/platform/RenderNodeLayer.kt
@@ -17,15 +17,16 @@
package androidx.compose.ui.platform
import android.annotation.TargetApi
-import android.graphics.Matrix
import android.graphics.RenderNode
import androidx.compose.ui.DrawLayerModifier
import androidx.compose.ui.TransformOrigin
import androidx.compose.ui.geometry.Size
import androidx.compose.ui.graphics.Canvas
import androidx.compose.ui.graphics.CanvasHolder
+import androidx.compose.ui.graphics.Matrix
import androidx.compose.ui.graphics.RectangleShape
import androidx.compose.ui.graphics.nativeCanvas
+import androidx.compose.ui.graphics.setFrom
import androidx.compose.ui.node.OwnedLayer
import androidx.compose.ui.unit.IntOffset
import androidx.compose.ui.unit.IntSize
@@ -46,7 +47,7 @@
private var isDirty = false
private val outlineResolver = OutlineResolver(ownerView.density)
private var isDestroyed = false
- private var cacheMatrix: Matrix? = null
+ private var androidMatrixCache: android.graphics.Matrix? = null
private var drawnWithZ = false
private val canvasHolder = CanvasHolder()
@@ -199,9 +200,11 @@
ownerView.dirtyLayers -= this
}
- override fun getMatrix(): Matrix {
- val matrix = cacheMatrix ?: Matrix().also { cacheMatrix = it }
- renderNode.getMatrix(matrix)
- return matrix
+ override fun getMatrix(matrix: Matrix) {
+ val androidMatrix = androidMatrixCache ?: android.graphics.Matrix().also {
+ androidMatrixCache = it
+ }
+ renderNode.getMatrix(androidMatrix)
+ matrix.setFrom(androidMatrix)
}
}
diff --git a/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/platform/ViewLayer.kt b/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/platform/ViewLayer.kt
index 7082b12..16c78aa 100644
--- a/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/platform/ViewLayer.kt
+++ b/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/platform/ViewLayer.kt
@@ -19,16 +19,18 @@
import android.os.Build
import android.view.View
import android.view.ViewOutlineProvider
-import androidx.compose.ui.DrawLayerModifier
-import androidx.compose.ui.TransformOrigin
import androidx.compose.ui.geometry.Size
import androidx.compose.ui.graphics.Canvas
import androidx.compose.ui.graphics.CanvasHolder
import androidx.compose.ui.graphics.Path
import androidx.compose.ui.graphics.RectangleShape
-import androidx.compose.ui.node.OwnedLayer
import androidx.compose.ui.unit.IntOffset
import androidx.compose.ui.unit.IntSize
+import androidx.compose.ui.DrawLayerModifier
+import androidx.compose.ui.TransformOrigin
+import androidx.compose.ui.graphics.Matrix
+import androidx.compose.ui.graphics.setFrom
+import androidx.compose.ui.node.OwnedLayer
import java.lang.reflect.Field
import java.lang.reflect.Method
@@ -214,6 +216,11 @@
// should not do anything. If we keep this, we get more redrawing than is necessary.
}
+ override fun getMatrix(matrix: Matrix) {
+ val androidMatrix = super.getMatrix()
+ matrix.setFrom(androidMatrix)
+ }
+
companion object {
val OutlineProvider = object : ViewOutlineProvider() {
override fun getOutline(view: View, outline: android.graphics.Outline) {
diff --git a/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/platform/Wrapper.kt b/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/platform/Wrapper.kt
index 9d76e34..d70305e 100644
--- a/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/platform/Wrapper.kt
+++ b/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/platform/Wrapper.kt
@@ -34,6 +34,7 @@
import androidx.compose.runtime.compositionFor
import androidx.compose.runtime.currentComposer
import androidx.compose.runtime.emptyContent
+import androidx.compose.runtime.tooling.InspectionTables
import androidx.compose.ui.Modifier
import androidx.compose.ui.R
import androidx.compose.ui.focus.ExperimentalFocus
@@ -250,6 +251,7 @@
private var addedToLifecycle: Lifecycle? = null
private var contentWaitingForCreated: @Composable () -> Unit = emptyContent()
+ @OptIn(InternalComposeApi::class)
override fun setContent(content: @Composable () -> Unit) {
owner.setOnViewTreeOwnersAvailable {
if (!disposed) {
@@ -261,22 +263,22 @@
if (lifecycle.currentState.isAtLeast(Lifecycle.State.CREATED)) {
original.setContent {
@Suppress("UNCHECKED_CAST")
- (owner.view.getTag(R.id.inspection_slot_table_set) as?
- MutableSet<SlotTable>)
- ?.let {
- val composer = currentComposer
- @OptIn(InternalComposeApi::class)
- composer.collectKeySourceInformation()
- it.add(composer.slotTable)
+ val inspectionTable =
+ owner.view.getTag(R.id.inspection_slot_table_set) as?
+ MutableSet<SlotTable>
+ if (inspectionTable != null) {
+ inspectionTable.add(currentComposer.slotTable)
+ }
+ Providers(InspectionTables provides inspectionTable) {
+ ProvideAndroidAmbients(owner) {
+ SelectionContainer(
+ modifier = Modifier.noConsumptionTapGestureFilter {
+ @OptIn(ExperimentalFocus::class)
+ owner.focusManager.clearFocus()
+ },
+ children = content
+ )
}
- ProvideAndroidAmbients(owner) {
- SelectionContainer(
- modifier = Modifier.noConsumptionTapGestureFilter {
- @OptIn(ExperimentalFocus::class)
- owner.focusManager.clearFocus()
- },
- children = content
- )
}
}
} else {
diff --git a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/graphics/vector/Vector.kt b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/graphics/vector/Vector.kt
index 71267b2..037114c 100644
--- a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/graphics/vector/Vector.kt
+++ b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/graphics/vector/Vector.kt
@@ -23,16 +23,16 @@
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.ColorFilter
import androidx.compose.ui.graphics.Path
+import androidx.compose.ui.graphics.PathFillType
import androidx.compose.ui.graphics.PathMeasure
import androidx.compose.ui.graphics.StrokeCap
import androidx.compose.ui.graphics.StrokeJoin
import androidx.compose.ui.graphics.drawscope.DrawScope
import androidx.compose.ui.graphics.drawscope.Stroke
import androidx.compose.ui.graphics.drawscope.withTransform
-import androidx.compose.ui.graphics.vectormath.Matrix4
+import androidx.compose.ui.graphics.Matrix
import androidx.compose.ui.unit.IntSize
import androidx.compose.ui.util.fastForEach
-import androidx.compose.ui.util.toRadians
import kotlin.math.ceil
const val DefaultGroupName = ""
@@ -63,6 +63,7 @@
val DefaultStrokeLineJoin = StrokeJoin.Miter
val DefaultTintBlendMode = BlendMode.SrcIn
val DefaultTintColor = Color.Transparent
+val DefaultFillType = PathFillType.NonZero
fun addPathNodes(pathStr: String?): List<PathNode> =
if (pathStr == null) {
@@ -197,6 +198,13 @@
invalidate()
}
+ var pathFillType: PathFillType = DefaultFillType
+ set(value) {
+ field = value
+ renderPath.fillType = value
+ invalidate()
+ }
+
var strokeAlpha: Float = 1.0f
set(value) {
field = value
@@ -331,7 +339,7 @@
internal class GroupComponent : VNode() {
- private var groupMatrix: Matrix4? = null
+ private var groupMatrix: Matrix? = null
private val children = mutableListOf<VNode>()
@@ -440,20 +448,20 @@
private var isMatrixDirty = true
private fun updateMatrix() {
- val matrix: Matrix4
+ val matrix: Matrix
val target = groupMatrix
if (target == null) {
- matrix = Matrix4()
+ matrix = Matrix()
groupMatrix = matrix
} else {
matrix = target
+ matrix.reset()
}
- matrix.assignColumns(Matrix4.identity())
// M = T(translationX + pivotX, translationY + pivotY) *
// R(rotation) * S(scaleX, scaleY) *
// T(-pivotX, -pivotY)
matrix.translate(translationX + pivotX, translationY + pivotY)
- matrix.rotateZ(radians = rotation.toRadians())
+ matrix.rotateZ(degrees = rotation)
matrix.scale(scaleX, scaleY, 1f)
matrix.translate(-pivotX, -pivotY)
}
diff --git a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/graphics/vector/VectorAsset.kt b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/graphics/vector/VectorAsset.kt
index a1a9d9a..2edfc0b 100644
--- a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/graphics/vector/VectorAsset.kt
+++ b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/graphics/vector/VectorAsset.kt
@@ -18,6 +18,7 @@
import androidx.compose.runtime.Immutable
import androidx.compose.ui.graphics.Brush
+import androidx.compose.ui.graphics.PathFillType
import androidx.compose.ui.graphics.StrokeCap
import androidx.compose.ui.graphics.StrokeJoin
import androidx.compose.ui.unit.Dp
@@ -195,6 +196,11 @@
val pathData: List<PathNode>,
/**
+ * Rule to determine how the interior of the path is to be calculated
+ */
+ val pathFillType: PathFillType,
+
+ /**
* Specifies the color or gradient used to fill the path
*/
val fill: Brush? = null,
@@ -271,6 +277,7 @@
if (trimPathStart != other.trimPathStart) return false
if (trimPathEnd != other.trimPathEnd) return false
if (trimPathOffset != other.trimPathOffset) return false
+ if (pathFillType != other.pathFillType) return false
if (pathData != other.pathData) return false
return true
@@ -290,6 +297,7 @@
result = 31 * result + trimPathStart.hashCode()
result = 31 * result + trimPathEnd.hashCode()
result = 31 * result + trimPathOffset.hashCode()
+ result = 31 * result + pathFillType.hashCode()
return result
}
}
diff --git a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/graphics/vector/VectorAssetBuilder.kt b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/graphics/vector/VectorAssetBuilder.kt
index f19f6c0..a34da35 100644
--- a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/graphics/vector/VectorAssetBuilder.kt
+++ b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/graphics/vector/VectorAssetBuilder.kt
@@ -17,6 +17,7 @@
package androidx.compose.ui.graphics.vector
import androidx.compose.ui.graphics.Brush
+import androidx.compose.ui.graphics.PathFillType
import androidx.compose.ui.graphics.StrokeCap
import androidx.compose.ui.graphics.StrokeJoin
import androidx.compose.ui.unit.Dp
@@ -141,6 +142,7 @@
* tree structure
*
* @param pathData path information to render the shape of the path
+ * @param pathFillType rule to determine how the interior of the path is to be calculated
* @param name the name of the path
* @param fill specifies the [Brush] used to fill the path
* @param fillAlpha the alpha to fill the path
@@ -163,6 +165,7 @@
*/
fun addPath(
pathData: List<PathNode>,
+ pathFillType: PathFillType = DefaultFillType,
name: String = DefaultPathName,
fill: Brush? = null,
fillAlpha: Float = 1.0f,
@@ -181,6 +184,7 @@
VectorPath(
name,
pathData,
+ pathFillType,
fill,
fillAlpha,
stroke,
@@ -295,9 +299,11 @@
strokeLineCap: StrokeCap = DefaultStrokeLineCap,
strokeLineJoin: StrokeJoin = DefaultStrokeLineJoin,
strokeLineMiter: Float = DefaultStrokeLineMiter,
+ pathFillType: PathFillType = DefaultFillType,
pathBuilder: PathBuilder.() -> Unit
) = addPath(
PathData(pathBuilder),
+ pathFillType,
name,
fill,
fillAlpha,
diff --git a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/graphics/vector/VectorCompose.kt b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/graphics/vector/VectorCompose.kt
index db9d82a..2c7420f 100644
--- a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/graphics/vector/VectorCompose.kt
+++ b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/graphics/vector/VectorCompose.kt
@@ -25,6 +25,7 @@
import androidx.compose.runtime.compositionFor
import androidx.compose.runtime.emit
import androidx.compose.ui.graphics.Brush
+import androidx.compose.ui.graphics.PathFillType
import androidx.compose.ui.graphics.StrokeCap
import androidx.compose.ui.graphics.StrokeJoin
@@ -62,6 +63,7 @@
@Composable
fun Path(
pathData: List<PathNode>,
+ pathFillType: PathFillType = DefaultFillType,
name: String = DefaultPathName,
fill: Brush? = null,
fillAlpha: Float = 1.0f,
@@ -80,6 +82,7 @@
update = {
set(name) { this.name = it }
set(pathData) { this.pathData = it }
+ set(pathFillType) { this.pathFillType = it }
set(fill) { this.fill = it }
set(fillAlpha) { this.fillAlpha = it }
set(stroke) { this.stroke = it }
diff --git a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/graphics/vector/VectorPainter.kt b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/graphics/vector/VectorPainter.kt
index 0a53b0e..c04bd07 100644
--- a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/graphics/vector/VectorPainter.kt
+++ b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/graphics/vector/VectorPainter.kt
@@ -174,6 +174,7 @@
if (vectorNode is VectorPath) {
Path(
pathData = vectorNode.pathData,
+ pathFillType = vectorNode.pathFillType,
name = vectorNode.name,
fill = vectorNode.fill,
fillAlpha = vectorNode.fillAlpha,
diff --git a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/input/pointer/HitPathTracker.kt b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/input/pointer/HitPathTracker.kt
index de2c242..590b7f03 100644
--- a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/input/pointer/HitPathTracker.kt
+++ b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/input/pointer/HitPathTracker.kt
@@ -419,7 +419,7 @@
// to onPointerEvent).
// Dispatch on the tunneling pass.
- internalPointerEvent.tryDispatchToPointerInputFilter(pointerInputFilter, downPass)
+ internalPointerEvent.dispatchToPointerInputFilter(pointerInputFilter, downPass)
// Dispatch to children.
if (pointerInputFilter.isAttached) {
@@ -427,7 +427,7 @@
}
// Dispatch on the bubbling pass.
- internalPointerEvent.tryDispatchToPointerInputFilter(pointerInputFilter, upPass)
+ internalPointerEvent.dispatchToPointerInputFilter(pointerInputFilter, upPass)
// Put all of the relevant changes that were in the internalPointerEvent back into all of
// the changes, and then set all of the changes back onto the internalPointerEvent.
@@ -507,7 +507,7 @@
*
* Is a no-op if [filter] is not attached or [pass] is null.
*/
- private fun InternalPointerEvent.tryDispatchToPointerInputFilter(
+ private fun InternalPointerEvent.dispatchToPointerInputFilter(
filter: PointerInputFilter,
pass: PointerEventPass?
) {
diff --git a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/LayerWrapper.kt b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/LayerWrapper.kt
index 9a0b2a6..d47af92 100644
--- a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/LayerWrapper.kt
+++ b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/LayerWrapper.kt
@@ -18,22 +18,21 @@
import androidx.compose.ui.DrawLayerModifier
import androidx.compose.ui.Placeable
+import androidx.compose.ui.geometry.MutableRect
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.geometry.Rect
import androidx.compose.ui.graphics.Canvas
+import androidx.compose.ui.graphics.Matrix
import androidx.compose.ui.input.pointer.PointerInputFilter
import androidx.compose.ui.layout.globalPosition
-import androidx.compose.ui.platform.NativeMatrix
-import androidx.compose.ui.platform.NativeRectF
import androidx.compose.ui.unit.Constraints
import androidx.compose.ui.unit.IntOffset
internal class LayerWrapper(
wrapped: LayoutNodeWrapper,
modifier: DrawLayerModifier
-) : DelegatingLayoutNodeWrapper<DrawLayerModifier>(wrapped, modifier) {
+) : DelegatingLayoutNodeWrapper<DrawLayerModifier>(wrapped, modifier), (Canvas) -> Unit {
private var _layer: OwnedLayer? = null
- private var layerDestroyed = false
// Do not invalidate itself on position change.
override val invalidateLayerOnBoundsChange get() = false
@@ -53,7 +52,7 @@
get() {
return _layer ?: layoutNode.requireOwner().createLayer(
modifier,
- wrapped::draw,
+ this,
invalidateParentLayer
).also {
_layer = it
@@ -61,10 +60,10 @@
}
}
- // TODO(mount): This cache isn't thread safe at all.
- private var positionCache: FloatArray? = null
// TODO (njawad): This cache matrix is not thread safe
- private var inverseMatrixCache: NativeMatrix? = null
+ private var _matrixCache: Matrix? = null
+ private val matrixCache: Matrix
+ get() = _matrixCache ?: Matrix().also { _matrixCache = it }
override fun performMeasure(constraints: Constraints): Placeable {
val placeable = super.performMeasure(constraints)
@@ -97,56 +96,29 @@
}
override fun fromParentPosition(position: Offset): Offset {
- val matrix = layer.getMatrix()
- val targetPosition =
- if (!matrix.isIdentity()) {
- val inverse = inverseMatrixCache ?: NativeMatrix().also { inverseMatrixCache = it }
- matrix.invert(inverse)
- mapPointsFromMatrix(inverse, position)
- } else {
- position
- }
+ val inverse = matrixCache
+ layer.getMatrix(inverse)
+ inverse.invert()
+ val targetPosition = inverse.map(position)
return super.fromParentPosition(targetPosition)
}
override fun toParentPosition(position: Offset): Offset {
- val matrix = layer.getMatrix()
- val targetPosition =
- if (!matrix.isIdentity()) {
- mapPointsFromMatrix(matrix, position)
- } else {
- position
- }
+ val matrix = matrixCache
+ val targetPosition = matrix.map(position)
return super.toParentPosition(targetPosition)
}
- /**
- * Return a transformed [Offset] based off of the provided matrix transformation
- * and untransformed position.
- */
- private fun mapPointsFromMatrix(matrix: NativeMatrix, position: Offset): Offset {
- val x = position.x
- val y = position.y
- val cache = positionCache
- val point = if (cache != null) {
- cache[0] = x
- cache[1] = y
- cache
- } else {
- floatArrayOf(x, y).also { positionCache = it }
+ override fun rectInParent(bounds: MutableRect) {
+ if (modifier.clip) {
+ bounds.intersect(0f, 0f, size.width.toFloat(), size.height.toFloat())
+ if (bounds.isEmpty) {
+ return
+ }
}
- matrix.mapPoints(point)
- return Offset(point[0], point[1])
- }
-
- override fun rectInParent(bounds: NativeRectF) {
- if (modifier.clip &&
- !bounds.intersect(0f, 0f, size.width.toFloat(), size.height.toFloat())
- ) {
- bounds.setEmpty()
- }
- val matrix = layer.getMatrix()
- matrix.mapRect(bounds)
+ val matrix = matrixCache
+ layer.getMatrix(matrix)
+ matrix.map(bounds)
return super.rectInParent(bounds)
}
@@ -179,4 +151,8 @@
override fun onModifierChanged() {
_layer?.invalidate()
}
+
+ override fun invoke(canvas: Canvas) {
+ wrapped.draw(canvas)
+ }
}
diff --git a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/LayoutNodeWrapper.kt b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/LayoutNodeWrapper.kt
index ff38376..da75e899 100644
--- a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/LayoutNodeWrapper.kt
+++ b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/LayoutNodeWrapper.kt
@@ -23,13 +23,14 @@
import androidx.compose.ui.Placeable
import androidx.compose.ui.focus.ExperimentalFocus
import androidx.compose.ui.focus.FocusState
+import androidx.compose.ui.geometry.MutableRect
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.geometry.Rect
+import androidx.compose.ui.geometry.toRect
import androidx.compose.ui.graphics.Canvas
import androidx.compose.ui.graphics.Paint
import androidx.compose.ui.input.pointer.PointerInputFilter
import androidx.compose.ui.layout.LayoutCoordinates
-import androidx.compose.ui.platform.NativeRectF
import androidx.compose.ui.unit.Constraints
import androidx.compose.ui.unit.IntOffset
import androidx.compose.ui.unit.IntSize
@@ -90,8 +91,10 @@
// wrapped, but is not interested in the position of the wrapped of the wrapped.
var isShallowPlacing = false
- // TODO(mount): This is not thread safe.
- private var rectCache: NativeRectF? = null
+ private var _rectCache: MutableRect? = null
+ private val rectCache: MutableRect get() = _rectCache ?: MutableRect(0f, 0f, 0f, 0f).also {
+ _rectCache = it
+ }
/**
* Whether a pointer that is relative to the device screen is in the bounds of this
@@ -244,7 +247,7 @@
* Modifies bounds to be in the parent LayoutNodeWrapper's coordinates, including clipping,
* scaling, etc.
*/
- protected open fun rectInParent(bounds: NativeRectF) {
+ protected open fun rectInParent(bounds: MutableRect) {
val x = position.x
bounds.left += x
bounds.right += x
@@ -257,16 +260,17 @@
override fun childBoundingBox(child: LayoutCoordinates): Rect {
check(isAttached) { ExpectAttachedLayoutCoordinates }
check(child.isAttached) { "Child $child is not attached!" }
- val rectF = rectCache ?: NativeRectF().also { rectCache = it }
- rectF.set(
- 0f,
- 0f,
- child.size.width.toFloat(),
- child.size.height.toFloat()
- )
+ val bounds = rectCache
+ bounds.left = 0f
+ bounds.top = 0f
+ bounds.right = child.size.width.toFloat()
+ bounds.bottom = child.size.height.toFloat()
var wrapper = child as LayoutNodeWrapper
while (wrapper !== this) {
- wrapper.rectInParent(rectF)
+ wrapper.rectInParent(bounds)
+ if (bounds.isEmpty) {
+ return Rect.Zero
+ }
val parent = wrapper.wrappedBy
check(parent != null) {
@@ -274,12 +278,7 @@
}
wrapper = parent
}
- return Rect(
- left = rectF.left,
- top = rectF.top,
- right = rectF.right,
- bottom = rectF.bottom
- )
+ return bounds.toRect()
}
/**
diff --git a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/OwnedLayer.kt b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/OwnedLayer.kt
index 901357d..cce781f 100644
--- a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/OwnedLayer.kt
+++ b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/OwnedLayer.kt
@@ -18,7 +18,7 @@
import androidx.compose.ui.DrawLayerModifier
import androidx.compose.ui.graphics.Canvas
-import androidx.compose.ui.platform.NativeMatrix
+import androidx.compose.ui.graphics.Matrix
import androidx.compose.ui.unit.IntOffset
import androidx.compose.ui.unit.IntSize
@@ -74,8 +74,7 @@
fun destroy()
/**
- * Returns a matrix that this layer will use to transform the contents.
- * The caller must not modify the returned Matrix.
+ * Modifies [matrix] to be the transform that this layer applies to its content.
*/
- fun getMatrix(): NativeMatrix
-}
\ No newline at end of file
+ fun getMatrix(matrix: Matrix)
+}
diff --git a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/platform/Expect.kt b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/platform/AtomicInt.kt
similarity index 62%
rename from compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/platform/Expect.kt
rename to compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/platform/AtomicInt.kt
index 6069c74..b5a0be1 100644
--- a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/platform/Expect.kt
+++ b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/platform/AtomicInt.kt
@@ -22,27 +22,4 @@
expect class AtomicInt(value_: Int) {
fun addAndGet(delta: Int): Int
fun compareAndSet(expected: Int, new: Int): Boolean
-}
-
-// TODO(b/160140398): rewrite depending code using androidx.compose.ui.geometry.Rect and androidx.ui.vectormath64.Matrix3.
-expect class NativeRectF() {
- var left: Float
- var right: Float
- var top: Float
- var bottom: Float
-
- fun set(left: Float, right: Float, top: Float, bottom: Float)
-
- fun intersect(left: Float, right: Float, top: Float, bottom: Float): Boolean
-
- fun setEmpty()
-}
-
-expect class NativeMatrix() {
- fun isIdentity(): Boolean
-
- fun invert(inverted: NativeMatrix): Boolean
-
- fun mapPoints(points: FloatArray)
- fun mapRect(rect: NativeRectF): Boolean
}
\ No newline at end of file
diff --git a/compose/desktop/desktop/src/jvmMain/kotlin/androidx/compose/desktop/ComposeInit.kt b/compose/ui/ui/src/desktopMain/kotlin/androidx/compose/desktop/ComposeInit.kt
similarity index 100%
rename from compose/desktop/desktop/src/jvmMain/kotlin/androidx/compose/desktop/ComposeInit.kt
rename to compose/ui/ui/src/desktopMain/kotlin/androidx/compose/desktop/ComposeInit.kt
diff --git a/compose/ui/ui/src/desktopMain/kotlin/androidx/compose/ui/input/mouse/MouseScrollFilter.kt b/compose/ui/ui/src/desktopMain/kotlin/androidx/compose/ui/input/mouse/MouseScrollFilter.kt
new file mode 100644
index 0000000..ba88a8e
--- /dev/null
+++ b/compose/ui/ui/src/desktopMain/kotlin/androidx/compose/ui/input/mouse/MouseScrollFilter.kt
@@ -0,0 +1,116 @@
+/*
+ * Copyright 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.compose.ui.input.mouse
+
+import androidx.compose.runtime.remember
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.composed
+import androidx.compose.ui.gesture.PointerInputModifierImpl
+import androidx.compose.ui.gesture.scrollorientationlocking.Orientation
+import androidx.compose.ui.input.pointer.PointerEventPass
+import androidx.compose.ui.input.pointer.PointerInputChange
+import androidx.compose.ui.input.pointer.PointerInputFilter
+import androidx.compose.ui.unit.IntSize
+
+/**
+ * Indicates distance by which we should scroll some container.
+ */
+sealed class MouseScrollUnit {
+ /**
+ * Indicates that scrolling should be performed by [value] lines.
+ *
+ * On different platforms one tick of wheel rotation may cause different [value].
+ *
+ * Scrolling by one line usually means that we should scroll by some fixed offset, or by offset
+ * dependent on the container's bounds (in which scroll event occurs),
+ * or by one real text line in some document.
+ */
+ data class Line(val value: Float) : MouseScrollUnit()
+
+ /**
+ * Indicates that scrolling should be performed by [value] pages.
+ *
+ * Some platforms don't emit scrolling events by Page units.
+ *
+ * Scrolling by one page usually means that we should scroll by one container's height
+ * (in which scroll event occurs), or by one real page in some document.
+ */
+ data class Page(val value: Float) : MouseScrollUnit()
+}
+
+/**
+ * Mouse wheel or touchpad event.
+ */
+class MouseScrollEvent(
+ /**
+ * Change of mouse scroll.
+ *
+ * Positive if scrolling down, negative if scrolling up.
+ */
+ val delta: MouseScrollUnit,
+
+ /**
+ * Orientation in which scrolling event occurs.
+ *
+ * Up/down wheel scrolling causes events in vertical orientation.
+ * Left/right wheel scrolling causes events in horizontal orientation.
+ */
+ val orientation: Orientation
+)
+
+/**
+ * Adding this [modifier][Modifier] to the [modifier][Modifier] parameter of a component will
+ * allow it to intercept scroll events from mouse wheel and touchpad.
+ *
+ * @param onMouseScroll This callback is invoked when the user interacts with the mouse wheel or
+ * touchpad.
+ * While implementing this callback, return true to stop propagation of this event. If you return
+ * false, the scroll event will be sent to this [mouseScrollFilter]'s parent.
+ */
+fun Modifier.mouseScrollFilter(
+ onMouseScroll: (
+ /**
+ * Mouse wheel or touchpad event.
+ */
+ event: MouseScrollEvent,
+
+ /**
+ * Bounds of the container in which scroll event occurs.
+ */
+ bounds: IntSize
+ ) -> Boolean
+): Modifier = composed {
+ val filter = remember(::MouseScrollEventFilter)
+ filter.onMouseScroll = onMouseScroll
+ PointerInputModifierImpl(filter)
+}
+
+internal class MouseScrollEventFilter : PointerInputFilter() {
+ lateinit var onMouseScroll: (MouseScrollEvent, IntSize) -> Boolean
+
+ override fun onPointerInput(
+ changes: List<PointerInputChange>,
+ pass: PointerEventPass,
+ bounds: IntSize
+ ) = changes
+
+ override fun onCancel() = Unit
+
+ fun onMouseScroll(event: MouseScrollEvent): Boolean {
+ return isAttached && onMouseScroll(event, size)
+ }
+}
\ No newline at end of file
diff --git a/compose/ui/ui/src/desktopMain/kotlin/androidx/compose/ui/platform/DesktopActuals.kt b/compose/ui/ui/src/desktopMain/kotlin/androidx/compose/ui/platform/DesktopActuals.kt
deleted file mode 100644
index b6fc696..0000000
--- a/compose/ui/ui/src/desktopMain/kotlin/androidx/compose/ui/platform/DesktopActuals.kt
+++ /dev/null
@@ -1,107 +0,0 @@
-/*
- * Copyright 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package androidx.compose.ui.platform
-
-import androidx.compose.ui.graphics.vectormath.Matrix3
-import androidx.compose.ui.graphics.vectormath.Vector3
-import androidx.compose.ui.graphics.vectormath.inverse
-
-actual class NativeRectF {
- actual var left: Float = 0f
- actual var right: Float = 0f
- actual var top: Float = 0f
- actual var bottom: Float = 0f
-
- actual fun set(left: Float, right: Float, top: Float, bottom: Float) {
- this.left = left
- this.right = right
- this.top = top
- this.bottom = bottom
- }
-
- /**
- * copy of android.graphics.RectF.intersect(float, float, float, float)
- */
- actual fun intersect(left: Float, right: Float, top: Float, bottom: Float): Boolean {
- if (this.left < right && left < this.right && this.top < bottom && top < this.bottom) {
- if (this.left < left) {
- this.left = left
- }
- if (this.top < top) {
- this.top = top
- }
- if (this.right > right) {
- this.right = right
- }
- if (this.bottom > bottom) {
- this.bottom = bottom
- }
- return true
- }
- return false
- }
-
- actual fun setEmpty() {
- left = 0f
- right = 0f
- top = 0f
- bottom = 0f
- }
-}
-
-actual class NativeMatrix {
- private var matrix = identityMatrix
-
- actual fun isIdentity(): Boolean = matrix == identityMatrix
-
- actual fun invert(inverted: NativeMatrix): Boolean {
- inverted.matrix = inverse(matrix)
- return true
- }
-
- actual fun mapPoints(points: FloatArray) {
- for (i in points.indices step 2) {
- val original = Vector3(points[i], points[i + 1])
- val result = matrix * original
- points[i] = result.x
- points[i + 1] = result.y
- }
- }
-
- actual fun mapRect(rect: NativeRectF): Boolean {
- val lt = Vector3(rect.left, rect.top, 1f)
- val lb = Vector3(rect.left, rect.bottom, 1f)
- val rt = Vector3(rect.right, rect.top, 1f)
- val rb = Vector3(rect.right, rect.bottom, 1f)
-
- val transformedLT = matrix * lt
- val transformedLB = matrix * lb
- val transformedRT = matrix * rt
- val transformedRB = matrix * rb
-
- rect.left = minOf(transformedLT.x, transformedLB.x, transformedRT.x, transformedRB.x)
- rect.right = maxOf(transformedLT.x, transformedLB.x, transformedRT.x, transformedRB.x)
- rect.top = minOf(transformedLT.y, transformedLB.y, transformedRT.y, transformedRB.y)
- rect.bottom = maxOf(transformedLT.y, transformedLB.y, transformedRT.y, transformedRB.y)
-
- return true
- }
-
- companion object {
- private val identityMatrix = Matrix3.identity()
- }
-}
\ No newline at end of file
diff --git a/compose/ui/ui/src/desktopMain/kotlin/androidx/compose/ui/platform/DesktopOwner.kt b/compose/ui/ui/src/desktopMain/kotlin/androidx/compose/ui/platform/DesktopOwner.kt
index 5147107..4974193 100644
--- a/compose/ui/ui/src/desktopMain/kotlin/androidx/compose/ui/platform/DesktopOwner.kt
+++ b/compose/ui/ui/src/desktopMain/kotlin/androidx/compose/ui/platform/DesktopOwner.kt
@@ -28,13 +28,17 @@
import androidx.compose.ui.drawLayer
import androidx.compose.ui.focus.ExperimentalFocus
import androidx.compose.ui.focus.FocusManager
+import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.graphics.Canvas
import androidx.compose.ui.graphics.DesktopCanvas
import androidx.compose.ui.input.key.ExperimentalKeyInput
import androidx.compose.ui.input.key.KeyEvent
import androidx.compose.ui.input.key.KeyInputModifier
+import androidx.compose.ui.input.mouse.MouseScrollEvent
+import androidx.compose.ui.input.mouse.MouseScrollEventFilter
import androidx.compose.ui.input.pointer.PointerInputEvent
import androidx.compose.ui.input.pointer.PointerInputEventProcessor
+import androidx.compose.ui.input.pointer.PointerInputFilter
import androidx.compose.ui.node.ExperimentalLayoutNodeApi
import androidx.compose.ui.node.InternalCoreApi
import androidx.compose.ui.node.LayoutNode
@@ -59,7 +63,9 @@
InternalCoreApi::class
)
class DesktopOwner(
- val container: DesktopOwners
+ val container: DesktopOwners,
+ // TODO(demin): pass density here instead of scale canvas (SkiaWindow.kt#initSkija)
+ override val density: Density = Density(1f, 1f)
) : Owner {
private var size: IntSize = IntSize(0, 0)
@@ -102,9 +108,6 @@
// we don't need to call root.detach() because root will be garbage collected
}
- // TODO(demin): pass density here instead of scale canvas (SkiaWindow.kt#initSkija)
- override val density = Density(1f, 1f)
-
override val textInputService = TextInputService(container.platformInputService)
override val fontLoader = FontLoader()
@@ -219,4 +222,22 @@
measureAndLayout()
pointerInputEventProcessor.process(event)
}
+
+ // TODO(demin): This is likely temporary. After PointerInputEvent can handle mouse events
+ // (scroll in particular), we can replace it with processPointerInput. see b/166105940
+ internal fun onMouseScroll(position: Offset, event: MouseScrollEvent) {
+ measureAndLayout()
+
+ val inputFilters = mutableListOf<PointerInputFilter>()
+ root.hitTest(position, inputFilters)
+
+ for (filter in inputFilters
+ .asReversed()
+ .asSequence()
+ .filterIsInstance<MouseScrollEventFilter>()
+ ) {
+ val isConsumed = filter.onMouseScroll(event)
+ if (isConsumed) break
+ }
+ }
}
\ No newline at end of file
diff --git a/compose/ui/ui/src/desktopMain/kotlin/androidx/compose/ui/platform/DesktopOwners.kt b/compose/ui/ui/src/desktopMain/kotlin/androidx/compose/ui/platform/DesktopOwners.kt
index 3db0d41..c96aa083 100644
--- a/compose/ui/ui/src/desktopMain/kotlin/androidx/compose/ui/platform/DesktopOwners.kt
+++ b/compose/ui/ui/src/desktopMain/kotlin/androidx/compose/ui/platform/DesktopOwners.kt
@@ -19,6 +19,7 @@
import androidx.compose.runtime.dispatch.DesktopUiDispatcher
import androidx.compose.runtime.staticAmbientOf
import androidx.compose.ui.geometry.Offset
+import androidx.compose.ui.input.mouse.MouseScrollEvent
import androidx.compose.ui.input.pointer.PointerId
import androidx.compose.ui.input.pointer.PointerInputData
import androidx.compose.ui.input.pointer.PointerInputEvent
@@ -79,6 +80,11 @@
list.lastOrNull()?.processPointerInput(pointerInputEvent(x, y, isMousePressed))
}
+ fun onMouseScroll(x: Int, y: Int, event: MouseScrollEvent) {
+ val position = Offset(x.toFloat(), y.toFloat())
+ list.lastOrNull()?.onMouseScroll(position, event)
+ }
+
fun onKeyPressed(code: Int, char: Char) {
platformInputService.onKeyPressed(code, char)
}
@@ -118,7 +124,7 @@
fun invalidate() {
if (!redrawingScheduled) {
- DesktopUiDispatcher.Dispatcher.scheduleAfterCallback {
+ DesktopUiDispatcher.Dispatcher.scheduleCallback {
redrawingScheduled = false
if (Recomposer.current().hasPendingChanges()) {
invalidate()
diff --git a/compose/ui/ui/src/desktopMain/kotlin/androidx/compose/ui/platform/DesktopPlatform.kt b/compose/ui/ui/src/desktopMain/kotlin/androidx/compose/ui/platform/DesktopPlatform.kt
new file mode 100644
index 0000000..2f3ae48
--- /dev/null
+++ b/compose/ui/ui/src/desktopMain/kotlin/androidx/compose/ui/platform/DesktopPlatform.kt
@@ -0,0 +1,45 @@
+/*
+ * Copyright 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.compose.ui.platform
+
+import androidx.compose.runtime.staticAmbientOf
+
+val DesktopPlatformAmbient = staticAmbientOf(::identifyCurrent)
+
+/**
+ * Identify OS on which the application is currently running.
+ *
+ * If it is needed to know the current platform in @Composable function,
+ * use [DesktopPlatformAmbient] instead of this function.
+ *
+ * identifyCurrent() should be used preferable only in initialization code.
+ */
+private fun identifyCurrent(): DesktopPlatform {
+ val name = System.getProperty("os.name")
+ return when {
+ name.startsWith("Linux") -> DesktopPlatform.Linux
+ name.startsWith("Win") -> DesktopPlatform.Windows
+ name == "Mac OS X" -> DesktopPlatform.MacOS
+ else -> throw Error("Unsupported OS $name")
+ }
+}
+
+enum class DesktopPlatform {
+ Linux,
+ Windows,
+ MacOS
+}
\ No newline at end of file
diff --git a/compose/ui/ui/src/desktopMain/kotlin/androidx/compose/ui/platform/DesktopSelection.kt b/compose/ui/ui/src/desktopMain/kotlin/androidx/compose/ui/platform/DesktopSelection.kt
index 7242073b..110003c 100644
--- a/compose/ui/ui/src/desktopMain/kotlin/androidx/compose/ui/platform/DesktopSelection.kt
+++ b/compose/ui/ui/src/desktopMain/kotlin/androidx/compose/ui/platform/DesktopSelection.kt
@@ -26,8 +26,6 @@
import androidx.compose.ui.gesture.DragObserver
import androidx.compose.ui.gesture.rawDragGestureFilter
import androidx.compose.ui.gesture.rawPressStartGestureFilter
-import androidx.compose.ui.input.pointer.PointerInputFilter
-import androidx.compose.ui.input.pointer.PointerInputModifier
import androidx.compose.ui.onPositioned
import androidx.compose.ui.selection.Selection
import androidx.compose.ui.selection.SelectionRegistrarAmbient
@@ -66,9 +64,6 @@
)
}
-private class PointerInputModifierImpl(override val pointerInputFilter: PointerInputFilter) :
- PointerInputModifier
-
private fun Modifier.selectionFilter(observer: DragObserver): Modifier = composed {
val glue = remember { DragGlue(observer) }
rawDragGestureFilter(glue, glue::started)
diff --git a/compose/ui/ui/src/desktopMain/kotlin/androidx/compose/ui/platform/SkijaLayer.kt b/compose/ui/ui/src/desktopMain/kotlin/androidx/compose/ui/platform/SkijaLayer.kt
index 73c49a1..7f66795 100644
--- a/compose/ui/ui/src/desktopMain/kotlin/androidx/compose/ui/platform/SkijaLayer.kt
+++ b/compose/ui/ui/src/desktopMain/kotlin/androidx/compose/ui/platform/SkijaLayer.kt
@@ -19,6 +19,7 @@
import androidx.compose.ui.DrawLayerModifier
import androidx.compose.ui.graphics.Canvas
import androidx.compose.ui.graphics.DesktopCanvas
+import androidx.compose.ui.graphics.Matrix
import androidx.compose.ui.graphics.Outline
import androidx.compose.ui.node.OwnedLayer
import androidx.compose.ui.unit.Density
@@ -51,7 +52,9 @@
}
// TODO(demin): calculate matrix
- override fun getMatrix() = NativeMatrix()
+ override fun getMatrix(matrix: Matrix) {
+ matrix.reset()
+ }
override fun invalidate() {
invalidateParentLayer()
@@ -101,4 +104,4 @@
override fun updateDisplayList() = Unit
override fun updateLayerProperties() = Unit
-}
\ No newline at end of file
+}
diff --git a/compose/ui/ui/src/desktopTest/kotlin/androidx/compose/ui/input/mouse/MouseScrollFilterTest.kt b/compose/ui/ui/src/desktopTest/kotlin/androidx/compose/ui/input/mouse/MouseScrollFilterTest.kt
new file mode 100644
index 0000000..15b4391d
--- /dev/null
+++ b/compose/ui/ui/src/desktopTest/kotlin/androidx/compose/ui/input/mouse/MouseScrollFilterTest.kt
@@ -0,0 +1,308 @@
+/*
+ * Copyright 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.compose.ui.input.mouse
+
+import androidx.compose.foundation.Box
+import androidx.compose.foundation.layout.size
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.gesture.scrollorientationlocking.Orientation
+import androidx.compose.ui.test.TestComposeWindow
+import androidx.compose.ui.unit.Density
+import androidx.compose.ui.unit.IntSize
+import androidx.compose.ui.unit.dp
+import com.google.common.truth.Truth.assertThat
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+
+@RunWith(JUnit4::class)
+class MouseScrollFilterTest {
+ private val window = TestComposeWindow(width = 100, height = 100, density = Density(2f))
+
+ @Test
+ fun `inside box`() {
+ var actualEvent: MouseScrollEvent? = null
+ var actualBounds: IntSize? = null
+
+ window.setContent {
+ Box(
+ Modifier
+ .mouseScrollFilter { event, bounds ->
+ actualEvent = event
+ actualBounds = bounds
+ true
+ }
+ .size(10.dp, 20.dp)
+ )
+ }
+
+ window.owners.onMouseScroll(
+ x = 0,
+ y = 0,
+ event = MouseScrollEvent(MouseScrollUnit.Line(3f), Orientation.Vertical)
+ )
+
+ assertThat(actualEvent?.delta).isEqualTo(MouseScrollUnit.Line(3f))
+ assertThat(actualEvent?.orientation).isEqualTo(Orientation.Vertical)
+ assertThat(actualBounds).isEqualTo(IntSize(20, 40))
+ }
+
+ @Test
+ fun `outside box`() {
+ var actualEvent: MouseScrollEvent? = null
+ var actualBounds: IntSize? = null
+
+ window.setContent {
+ Box(
+ Modifier
+ .mouseScrollFilter { event, bounds ->
+ actualEvent = event
+ actualBounds = bounds
+ true
+ }
+ .size(10.dp, 20.dp)
+ )
+ }
+
+ window.owners.onMouseScroll(
+ x = 20,
+ y = 0,
+ event = MouseScrollEvent(MouseScrollUnit.Line(3f), Orientation.Vertical)
+ )
+
+ assertThat(actualEvent).isEqualTo(null)
+ assertThat(actualBounds).isEqualTo(null)
+ }
+
+ @Test
+ fun `inside two overlapping boxes`() {
+ var actualEvent1: MouseScrollEvent? = null
+ var actualBounds1: IntSize? = null
+ var actualEvent2: MouseScrollEvent? = null
+ var actualBounds2: IntSize? = null
+
+ window.setContent {
+ Box(
+ Modifier
+ .mouseScrollFilter { event, bounds ->
+ actualEvent1 = event
+ actualBounds1 = bounds
+ true
+ }
+ .size(10.dp, 20.dp)
+ )
+ Box(
+ Modifier
+ .mouseScrollFilter { event, bounds ->
+ actualEvent2 = event
+ actualBounds2 = bounds
+ true
+ }
+ .size(5.dp, 10.dp)
+ )
+ }
+
+ window.owners.onMouseScroll(
+ x = 0,
+ y = 0,
+ event = MouseScrollEvent(MouseScrollUnit.Line(3f), Orientation.Horizontal)
+ )
+
+ assertThat(actualEvent1).isEqualTo(null)
+ assertThat(actualBounds1).isEqualTo(null)
+ assertThat(actualEvent2?.delta).isEqualTo(MouseScrollUnit.Line(3f))
+ assertThat(actualEvent2?.orientation).isEqualTo(Orientation.Horizontal)
+ assertThat(actualBounds2).isEqualTo(IntSize(10, 20))
+ }
+
+ @Test
+ fun `inside two overlapping boxes, top box doesn't handle scroll`() {
+ var actualEvent: MouseScrollEvent? = null
+ var actualBounds: IntSize? = null
+
+ window.setContent {
+ Box(
+ Modifier
+ .mouseScrollFilter { event, bounds ->
+ actualEvent = event
+ actualBounds = bounds
+ true
+ }
+ .size(10.dp, 20.dp)
+ )
+ Box(
+ Modifier
+ .mouseScrollFilter { event, bounds ->
+ false
+ }
+ .size(5.dp, 10.dp)
+ )
+ }
+
+ window.owners.onMouseScroll(
+ x = 0,
+ y = 0,
+ event = MouseScrollEvent(MouseScrollUnit.Line(3f), Orientation.Horizontal)
+ )
+
+ assertThat(actualEvent).isEqualTo(null)
+ assertThat(actualBounds).isEqualTo(null)
+ }
+
+ @Test
+ fun `inside two overlapping boxes, top box doesn't have mouseScrollFilter`() {
+ var actualEvent: MouseScrollEvent? = null
+ var actualBounds: IntSize? = null
+
+ window.setContent {
+ Box(
+ Modifier
+ .mouseScrollFilter { event, bounds ->
+ actualEvent = event
+ actualBounds = bounds
+ true
+ }
+ .size(10.dp, 20.dp)
+ )
+ Box(
+ Modifier
+ .size(5.dp, 10.dp)
+ )
+ }
+
+ window.owners.onMouseScroll(
+ x = 0,
+ y = 0,
+ event = MouseScrollEvent(MouseScrollUnit.Line(3f), Orientation.Horizontal)
+ )
+
+ assertThat(actualEvent?.delta).isEqualTo(MouseScrollUnit.Line(3f))
+ assertThat(actualEvent?.orientation).isEqualTo(Orientation.Horizontal)
+ assertThat(actualBounds).isEqualTo(IntSize(20, 40))
+ }
+
+ @Test
+ fun `inside two nested boxes`() {
+ var actualEvent1: MouseScrollEvent? = null
+ var actualBounds1: IntSize? = null
+ var actualEvent2: MouseScrollEvent? = null
+ var actualBounds2: IntSize? = null
+
+ window.setContent {
+ Box(
+ Modifier
+ .mouseScrollFilter { event, bounds ->
+ actualEvent1 = event
+ actualBounds1 = bounds
+ true
+ }
+ .size(10.dp, 20.dp)
+ ) {
+ Box(
+ Modifier
+ .mouseScrollFilter { event, bounds ->
+ actualEvent2 = event
+ actualBounds2 = bounds
+ true
+ }
+ .size(5.dp, 10.dp)
+ )
+ }
+ }
+
+ window.owners.onMouseScroll(
+ x = 0,
+ y = 0,
+ event = MouseScrollEvent(MouseScrollUnit.Line(-1f), Orientation.Horizontal)
+ )
+
+ assertThat(actualEvent1).isEqualTo(null)
+ assertThat(actualBounds1).isEqualTo(null)
+ assertThat(actualEvent2?.delta).isEqualTo(MouseScrollUnit.Line(-1f))
+ assertThat(actualEvent2?.orientation).isEqualTo(Orientation.Horizontal)
+ assertThat(actualBounds2).isEqualTo(IntSize(10, 20))
+ }
+
+ @Test
+ fun `inside two nested boxes, nested box doesn't handle scroll`() {
+ var actualEvent: MouseScrollEvent? = null
+ var actualBounds: IntSize? = null
+
+ window.setContent {
+ Box(
+ Modifier
+ .mouseScrollFilter { event, bounds ->
+ actualEvent = event
+ actualBounds = bounds
+ true
+ }
+ .size(10.dp, 20.dp)
+ ) {
+ Box(
+ Modifier
+ .mouseScrollFilter { event, bounds ->
+ false
+ }
+ .size(5.dp, 10.dp)
+ )
+ }
+ }
+
+ window.owners.onMouseScroll(
+ x = 0,
+ y = 0,
+ event = MouseScrollEvent(MouseScrollUnit.Page(1f), Orientation.Horizontal)
+ )
+
+ assertThat(actualEvent?.delta).isEqualTo(MouseScrollUnit.Page(1f))
+ assertThat(actualEvent?.orientation).isEqualTo(Orientation.Horizontal)
+ assertThat(actualBounds).isEqualTo(IntSize(20, 40))
+ }
+
+ @Test
+ fun `inside two nested boxes, nested box doesn't have mouseScrollFilter`() {
+ var actualEvent: MouseScrollEvent? = null
+ var actualBounds: IntSize? = null
+
+ window.setContent {
+ Box(
+ Modifier
+ .mouseScrollFilter { event, bounds ->
+ actualEvent = event
+ actualBounds = bounds
+ true
+ }
+ .size(10.dp, 20.dp)
+ ) {
+ Box(
+ Modifier
+ .size(5.dp, 10.dp)
+ )
+ }
+ }
+
+ window.owners.onMouseScroll(
+ x = 0,
+ y = 0,
+ event = MouseScrollEvent(MouseScrollUnit.Page(1f), Orientation.Horizontal)
+ )
+
+ assertThat(actualEvent?.delta).isEqualTo(MouseScrollUnit.Page(1f))
+ assertThat(actualEvent?.orientation).isEqualTo(Orientation.Horizontal)
+ assertThat(actualBounds).isEqualTo(IntSize(20, 40))
+ }
+}
\ No newline at end of file
diff --git a/compose/ui/ui/src/desktopTest/kotlin/androidx/compose/ui/test/TestComposeWindow.kt b/compose/ui/ui/src/desktopTest/kotlin/androidx/compose/ui/test/TestComposeWindow.kt
new file mode 100644
index 0000000..bd0cd7e1
--- /dev/null
+++ b/compose/ui/ui/src/desktopTest/kotlin/androidx/compose/ui/test/TestComposeWindow.kt
@@ -0,0 +1,61 @@
+/*
+ * Copyright 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package androidx.compose.ui.test
+
+import androidx.compose.desktop.initCompose
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.Providers
+import androidx.compose.ui.platform.DesktopOwner
+import androidx.compose.ui.platform.DesktopOwners
+import androidx.compose.ui.platform.DesktopPlatform
+import androidx.compose.ui.platform.DesktopPlatformAmbient
+import androidx.compose.ui.platform.setContent
+import androidx.compose.ui.unit.Density
+import org.jetbrains.skija.Surface
+import java.awt.Component
+
+// TODO(demin): move to :ui:ui-test after it will have desktopMain source set
+internal class TestComposeWindow(
+ val width: Int,
+ val height: Int,
+ val density: Density = Density(1f, 1f),
+ var desktopPlatform: DesktopPlatform = DesktopPlatform.Linux
+) {
+ val surface = Surface.makeRasterN32Premul(width, height)
+ val canvas = surface.canvas
+ val component = object : Component() {}
+ val owners = DesktopOwners(component = component, redraw = {})
+
+ fun setContent(content: @Composable () -> Unit): DesktopOwners {
+ val owner = DesktopOwner(owners, density)
+ owner.setContent {
+ Providers(
+ DesktopPlatformAmbient provides desktopPlatform
+ ) {
+ content()
+ }
+ }
+ owner.setSize(width, height)
+ owner.draw(canvas)
+ return owners
+ }
+
+ companion object {
+ init {
+ initCompose()
+ }
+ }
+}
\ No newline at end of file
diff --git a/contentpager/contentpager/api/api_lint.ignore b/contentpager/contentpager/api/api_lint.ignore
index f99d8fd..9d2911d 100644
--- a/contentpager/contentpager/api/api_lint.ignore
+++ b/contentpager/contentpager/api/api_lint.ignore
@@ -9,6 +9,10 @@
Inconsistent extra value; expected `androidx.contentpager.content.extra.TOTAL_COUNT`, was `android.content.extra.TOTAL_COUNT`
+CallbackMethodName: androidx.contentpager.content.ContentPager.QueryRunner.Callback#runQueryInBackground(androidx.contentpager.content.Query):
+ Callback method names must follow the on<Something> style: runQueryInBackground
+
+
ExecutorRegistration: androidx.contentpager.content.ContentPager#query(android.net.Uri, String[], android.os.Bundle, android.os.CancellationSignal, androidx.contentpager.content.ContentPager.ContentCallback):
Registration methods should have overload that accepts delivery Executor: `query`
ExecutorRegistration: androidx.contentpager.content.ContentPager.QueryRunner#query(androidx.contentpager.content.Query, androidx.contentpager.content.ContentPager.QueryRunner.Callback):
diff --git a/core/core/src/androidTest/java/androidx/core/content/res/ResourcesCompatTest.java b/core/core/src/androidTest/java/androidx/core/content/res/ResourcesCompatTest.java
index 1cf27cd..2ade066 100644
--- a/core/core/src/androidTest/java/androidx/core/content/res/ResourcesCompatTest.java
+++ b/core/core/src/androidTest/java/androidx/core/content/res/ResourcesCompatTest.java
@@ -378,6 +378,11 @@
assertNotSame(Typeface.DEFAULT, callback.mTypeface);
}
+ @Test(expected = Resources.NotFoundException.class)
+ public void testGetFont_brokenFont() {
+ ResourcesCompat.getFont(mContext, R.font.invalid_font);
+ }
+
@Test
public void testGetFont_xmlProviderFile_sync() {
Typeface font = ResourcesCompat.getFont(mContext, R.font.samplexmldownloadedfont);
diff --git a/core/core/src/androidTest/java/androidx/core/view/AccessibilityDelegateCompatTest.java b/core/core/src/androidTest/java/androidx/core/view/AccessibilityDelegateCompatTest.java
index 78dc818..4af717c 100644
--- a/core/core/src/androidTest/java/androidx/core/view/AccessibilityDelegateCompatTest.java
+++ b/core/core/src/androidTest/java/androidx/core/view/AccessibilityDelegateCompatTest.java
@@ -18,6 +18,7 @@
import static org.hamcrest.CoreMatchers.equalTo;
import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.CoreMatchers.not;
import static org.hamcrest.CoreMatchers.nullValue;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
@@ -50,6 +51,7 @@
import android.view.accessibility.AccessibilityNodeInfo;
import android.view.accessibility.AccessibilityNodeProvider;
+import androidx.core.view.accessibility.AccessibilityEventCompat;
import androidx.core.view.accessibility.AccessibilityNodeInfoCompat;
import androidx.core.view.accessibility.AccessibilityNodeInfoCompat.AccessibilityActionCompat;
import androidx.core.view.accessibility.AccessibilityNodeProviderCompat;
@@ -191,6 +193,92 @@
}
@Test
+ @SdkSuppress(minSdkVersion = 19)
+ public void testAccessibilityPaneTitle_isSentOnAppearance() throws Throwable {
+ final CharSequence title = "Sample title";
+ ViewCompat.setAccessibilityPaneTitle(mView, title);
+ mActivityTestRule.runOnUiThread(new Runnable() {
+ @Override
+ public void run() {
+ // Update the AccessibilityPaneVisibilityManager
+ mView.setVisibility(View.INVISIBLE);
+ mView.getViewTreeObserver().dispatchOnGlobalLayout();
+ mView.setVisibility(View.VISIBLE);
+ }
+ });
+
+ final AccessibilityDelegateCompat mockDelegate = mock(
+ AccessibilityDelegateCompat.class);
+ ViewCompat.setAccessibilityDelegate(mView, new BridgingDelegateCompat(mockDelegate));
+
+ mView.getViewTreeObserver().dispatchOnGlobalLayout();
+
+ ArgumentCaptor<AccessibilityEvent> argumentCaptor =
+ ArgumentCaptor.forClass(AccessibilityEvent.class);
+ if (Build.VERSION.SDK_INT < 28) {
+ // Validity check
+ assertThat(ViewCompat.getImportantForAccessibility(mView),
+ equalTo(ViewCompat.IMPORTANT_FOR_ACCESSIBILITY_YES));
+
+ verify(mockDelegate).sendAccessibilityEventUnchecked(
+ eq(mView), argumentCaptor.capture());
+ AccessibilityEvent event = argumentCaptor.getValue();
+ assertThat(event.getText().get(0), equalTo(title));
+ assertThat((event.getContentChangeTypes()
+ & AccessibilityEventCompat.CONTENT_CHANGE_TYPE_PANE_APPEARED), not(0));
+ } else {
+ verify(mockDelegate, never()).sendAccessibilityEventUnchecked(
+ eq(mView), argumentCaptor.capture());
+ }
+ }
+
+ @Test
+ @SdkSuppress(minSdkVersion = 19)
+ public void testAccessibilityPaneTitle_isSentOnDisappearance() throws Throwable {
+ final CharSequence title = "Sample title";
+ ViewCompat.setAccessibilityPaneTitle(mView, title);
+
+ // Validity check
+ assertThat(ViewCompat.getImportantForAccessibility(mView),
+ equalTo(ViewCompat.IMPORTANT_FOR_ACCESSIBILITY_YES));
+
+ final Activity activity = mActivityTestRule.getActivity();
+ sUiAutomation.executeAndWaitForEvent(new Runnable() {
+ @Override
+ public void run() {
+ try {
+ mActivityTestRule.runOnUiThread(new Runnable() {
+ @Override
+ public void run() {
+ // Update the AccessibilityPaneVisibilityManager
+ mView.setVisibility(View.INVISIBLE);
+ mView.getViewTreeObserver().dispatchOnGlobalLayout();
+ }
+ });
+ } catch (Throwable throwable) {
+ throwable.printStackTrace();
+ }
+ }
+ }, new UiAutomation.AccessibilityEventFilter() {
+ @Override
+ public boolean accept(AccessibilityEvent event) {
+ boolean isWindowStateChanged = event.getEventType()
+ == AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED;
+ int isPaneTitle = (event.getContentChangeTypes()
+ & AccessibilityEvent.CONTENT_CHANGE_TYPE_PANE_DISAPPEARED);
+ // onInitializeA11yEvent was not called in 28, so package name was not set
+ boolean isFromThisPackage = Build.VERSION.SDK_INT == 28
+ || TextUtils.equals(event.getPackageName(), activity.getPackageName());
+ boolean hasTitleText = event.getText().get(0).equals(title);
+ return isWindowStateChanged
+ && (isPaneTitle != 0)
+ && isFromThisPackage
+ && hasTitleText;
+ }
+ }, TIMEOUT_ASYNC_PROCESSING);
+ }
+
+ @Test
@SdkSuppress(minSdkVersion = 19, maxSdkVersion = 25)
public void testPerformSpanAction() {
final ClickableSpan span1 = mock(ClickableSpan.class);
diff --git a/core/core/src/main/java/androidx/core/graphics/TypefaceCompatApi29Impl.java b/core/core/src/main/java/androidx/core/graphics/TypefaceCompatApi29Impl.java
index 64c6436..ee8b22b 100644
--- a/core/core/src/main/java/androidx/core/graphics/TypefaceCompatApi29Impl.java
+++ b/core/core/src/main/java/androidx/core/graphics/TypefaceCompatApi29Impl.java
@@ -61,39 +61,43 @@
@NonNull FontsContractCompat.FontInfo[] fonts, int style) {
FontFamily.Builder familyBuilder = null;
final ContentResolver resolver = context.getContentResolver();
- for (FontsContractCompat.FontInfo font : fonts) {
- try (ParcelFileDescriptor pfd = resolver.openFileDescriptor(font.getUri(), "r",
- cancellationSignal)) {
- if (pfd == null) {
- continue; // keep adding succeeded fonts.
+ try {
+ for (FontsContractCompat.FontInfo font : fonts) {
+ try (ParcelFileDescriptor pfd = resolver.openFileDescriptor(font.getUri(), "r",
+ cancellationSignal)) {
+ if (pfd == null) {
+ continue; // keep adding succeeded fonts.
+ }
+ final Font platformFont = new Font.Builder(pfd)
+ .setWeight(font.getWeight())
+ .setSlant(font.isItalic() ? FontStyle.FONT_SLANT_ITALIC
+ : FontStyle.FONT_SLANT_UPRIGHT)
+ .setTtcIndex(font.getTtcIndex())
+ .build(); // TODO: font variation settings?
+ if (familyBuilder == null) {
+ familyBuilder = new FontFamily.Builder(platformFont);
+ } else {
+ familyBuilder.addFont(platformFont);
+ }
+ } catch (IOException e) {
+ // keep adding succeeded fonts.
}
- final Font platformFont = new Font.Builder(pfd)
- .setWeight(font.getWeight())
- .setSlant(font.isItalic() ? FontStyle.FONT_SLANT_ITALIC
- : FontStyle.FONT_SLANT_UPRIGHT)
- .setTtcIndex(font.getTtcIndex())
- .build(); // TODO: font variation settings?
- if (familyBuilder == null) {
- familyBuilder = new FontFamily.Builder(platformFont);
- } else {
- familyBuilder.addFont(platformFont);
- }
- } catch (IOException e) {
- // keep adding succeeded fonts.
}
+ if (familyBuilder == null) {
+ return null; // No font is added. Give up.
+ }
+ final FontStyle defaultStyle = new FontStyle(
+ (style & Typeface.BOLD) != 0 ? FontStyle.FONT_WEIGHT_BOLD
+ : FontStyle.FONT_WEIGHT_NORMAL,
+ (style & Typeface.ITALIC) != 0 ? FontStyle.FONT_SLANT_ITALIC
+ : FontStyle.FONT_SLANT_UPRIGHT
+ );
+ return new Typeface.CustomFallbackBuilder(familyBuilder.build())
+ .setStyle(defaultStyle)
+ .build();
+ } catch (Exception e) {
+ return null;
}
- if (familyBuilder == null) {
- return null; // No font is added. Give up.
- }
- final FontStyle defaultStyle = new FontStyle(
- (style & Typeface.BOLD) != 0 ? FontStyle.FONT_WEIGHT_BOLD
- : FontStyle.FONT_WEIGHT_NORMAL,
- (style & Typeface.ITALIC) != 0 ? FontStyle.FONT_SLANT_ITALIC
- : FontStyle.FONT_SLANT_UPRIGHT
- );
- return new Typeface.CustomFallbackBuilder(familyBuilder.build())
- .setStyle(defaultStyle)
- .build();
}
@Nullable
@@ -101,37 +105,41 @@
public Typeface createFromFontFamilyFilesResourceEntry(Context context,
FontResourcesParserCompat.FontFamilyFilesResourceEntry familyEntry, Resources resources,
int style) {
- FontFamily.Builder familyBuilder = null;
- for (FontResourcesParserCompat.FontFileResourceEntry entry : familyEntry.getEntries()) {
- try {
- final Font platformFont = new Font.Builder(resources, entry.getResourceId())
- .setWeight(entry.getWeight())
- .setSlant(entry.isItalic() ? FontStyle.FONT_SLANT_ITALIC
- : FontStyle.FONT_SLANT_UPRIGHT)
- .setTtcIndex(entry.getTtcIndex())
- .setFontVariationSettings(entry.getVariationSettings())
- .build();
- if (familyBuilder == null) {
- familyBuilder = new FontFamily.Builder(platformFont);
- } else {
- familyBuilder.addFont(platformFont);
+ try {
+ FontFamily.Builder familyBuilder = null;
+ for (FontResourcesParserCompat.FontFileResourceEntry entry : familyEntry.getEntries()) {
+ try {
+ final Font platformFont = new Font.Builder(resources, entry.getResourceId())
+ .setWeight(entry.getWeight())
+ .setSlant(entry.isItalic() ? FontStyle.FONT_SLANT_ITALIC
+ : FontStyle.FONT_SLANT_UPRIGHT)
+ .setTtcIndex(entry.getTtcIndex())
+ .setFontVariationSettings(entry.getVariationSettings())
+ .build();
+ if (familyBuilder == null) {
+ familyBuilder = new FontFamily.Builder(platformFont);
+ } else {
+ familyBuilder.addFont(platformFont);
+ }
+ } catch (IOException e) {
+ // keep adding succeeded fonts
}
- } catch (IOException e) {
- // keep adding succeeded fonts
}
+ if (familyBuilder == null) {
+ return null; // No font is added. Give up.
+ }
+ final FontStyle defaultStyle = new FontStyle(
+ (style & Typeface.BOLD) != 0 ? FontStyle.FONT_WEIGHT_BOLD
+ : FontStyle.FONT_WEIGHT_NORMAL,
+ (style & Typeface.ITALIC) != 0 ? FontStyle.FONT_SLANT_ITALIC
+ : FontStyle.FONT_SLANT_UPRIGHT
+ );
+ return new Typeface.CustomFallbackBuilder(familyBuilder.build())
+ .setStyle(defaultStyle)
+ .build();
+ } catch (Exception e) {
+ return null;
}
- if (familyBuilder == null) {
- return null; // No font is added. Give up
- }
- final FontStyle defaultStyle = new FontStyle(
- (style & Typeface.BOLD) != 0 ? FontStyle.FONT_WEIGHT_BOLD
- : FontStyle.FONT_WEIGHT_NORMAL,
- (style & Typeface.ITALIC) != 0 ? FontStyle.FONT_SLANT_ITALIC
- : FontStyle.FONT_SLANT_UPRIGHT
- );
- return new Typeface.CustomFallbackBuilder(familyBuilder.build())
- .setStyle(defaultStyle)
- .build();
}
/**
@@ -146,13 +154,12 @@
try {
font = new Font.Builder(resources, id).build();
family = new FontFamily.Builder(font).build();
- } catch (IOException e) {
+ return new Typeface.CustomFallbackBuilder(family)
+ // Set font's style to the display style for backward compatibility.
+ .setStyle(font.getStyle())
+ .build();
+ } catch (Exception e) {
return null;
}
- return new Typeface.CustomFallbackBuilder(family)
- // Set font's style to the display style for backward compatibility.
- .setStyle(font.getStyle())
- .build();
}
-
}
diff --git a/core/core/src/main/java/androidx/core/view/ViewCompat.java b/core/core/src/main/java/androidx/core/view/ViewCompat.java
index 3e58219..9e64e82 100644
--- a/core/core/src/main/java/androidx/core/view/ViewCompat.java
+++ b/core/core/src/main/java/androidx/core/view/ViewCompat.java
@@ -4230,16 +4230,31 @@
if (!accessibilityManager.isEnabled()) {
return;
}
- boolean isAccessibilityPane = getAccessibilityPaneTitle(view) != null;
+ boolean isVisibleAccessibilityPane = getAccessibilityPaneTitle(view) != null
+ && view.getVisibility() == View.VISIBLE;
// If this is a live region or accessibilityPane, we should send a subtree change event
// from this view immediately. Otherwise, we can let it propagate up.
if ((getAccessibilityLiveRegion(view) != ACCESSIBILITY_LIVE_REGION_NONE)
- || (isAccessibilityPane && view.getVisibility() == View.VISIBLE)) {
+ || isVisibleAccessibilityPane) {
final AccessibilityEvent event = AccessibilityEvent.obtain();
- event.setEventType(isAccessibilityPane ? AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED
+ event.setEventType(isVisibleAccessibilityPane
+ ? AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED
: AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED);
event.setContentChangeTypes(changeType);
+ if (isVisibleAccessibilityPane) {
+ event.getText().add(getAccessibilityPaneTitle(view));
+ setViewImportanceForAccessibilityIfNeeded(view);
+ }
view.sendAccessibilityEventUnchecked(event);
+ } else if (changeType == AccessibilityEvent.CONTENT_CHANGE_TYPE_PANE_DISAPPEARED) {
+ final AccessibilityEvent event = AccessibilityEvent.obtain();
+ view.onInitializeAccessibilityEvent(event);
+ event.setEventType(AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED);
+ event.setContentChangeTypes(changeType);
+ event.setSource(view);
+ view.onPopulateAccessibilityEvent(event);
+ event.getText().add(getAccessibilityPaneTitle(view));
+ accessibilityManager.sendAccessibilityEvent(event);
} else if (view.getParent() != null) {
try {
view.getParent().notifySubtreeAccessibilityStateChanged(view, view, changeType);
@@ -4250,6 +4265,25 @@
}
}
+ private static void setViewImportanceForAccessibilityIfNeeded(View view) {
+ if (ViewCompat.getImportantForAccessibility(view)
+ == ViewCompat.IMPORTANT_FOR_ACCESSIBILITY_AUTO) {
+ ViewCompat.setImportantForAccessibility(view,
+ ViewCompat.IMPORTANT_FOR_ACCESSIBILITY_YES);
+ }
+ // Check parent mode to ensure we're not hidden.
+ ViewParent parent = view.getParent();
+ while (parent instanceof View) {
+ if (ViewCompat.getImportantForAccessibility((View) parent)
+ == ViewCompat.IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS) {
+ ViewCompat.setImportantForAccessibility(view,
+ ViewCompat.IMPORTANT_FOR_ACCESSIBILITY_NO);
+ break;
+ }
+ parent = parent.getParent();
+ }
+ }
+
private static AccessibilityPaneVisibilityManager sAccessibilityPaneVisibilityManager =
new AccessibilityPaneVisibilityManager();
@@ -4260,8 +4294,10 @@
@RequiresApi(19)
@Override
public void onGlobalLayout() {
- for (Map.Entry<View, Boolean> entry : mPanesToVisible.entrySet()) {
- checkPaneVisibility(entry.getKey(), entry.getValue());
+ if (Build.VERSION.SDK_INT < 28) {
+ for (Map.Entry<View, Boolean> entry : mPanesToVisible.entrySet()) {
+ checkPaneVisibility(entry.getKey(), entry.getValue());
+ }
}
}
@@ -4297,10 +4333,10 @@
private void checkPaneVisibility(View pane, boolean oldVisibility) {
boolean newVisibility = pane.getVisibility() == View.VISIBLE;
if (oldVisibility != newVisibility) {
- if (newVisibility) {
- notifyViewAccessibilityStateChangedIfNeeded(pane,
- AccessibilityEvent.CONTENT_CHANGE_TYPE_PANE_APPEARED);
- }
+ int contentChangeType = newVisibility
+ ? AccessibilityEvent.CONTENT_CHANGE_TYPE_PANE_APPEARED
+ : AccessibilityEvent.CONTENT_CHANGE_TYPE_PANE_DISAPPEARED;
+ notifyViewAccessibilityStateChangedIfNeeded(pane, contentChangeType);
mPanesToVisible.put(pane, newVisibility);
}
}
diff --git a/core/core/src/main/java/androidx/core/view/WindowInsetsCompat.java b/core/core/src/main/java/androidx/core/view/WindowInsetsCompat.java
index efb74ee..164412c 100644
--- a/core/core/src/main/java/androidx/core/view/WindowInsetsCompat.java
+++ b/core/core/src/main/java/androidx/core/view/WindowInsetsCompat.java
@@ -44,6 +44,8 @@
import java.lang.annotation.RetentionPolicy;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
import java.util.Objects;
/**
@@ -73,6 +75,13 @@
@NonNull
public static final WindowInsetsCompat CONSUMED;
+ private static boolean sVisibleRectReflectionFetched = false;
+ private static Method sGetViewRootImplMethod;
+ private static Class<?> sViewRootImplClass;
+ private static Class<?> sAttachInfoClass;
+ private static Field sVisibleInsetsField;
+ private static Field sAttachInfoField;
+
static {
if (SDK_INT >= 30) {
CONSUMED = Impl30.CONSUMED;
@@ -1953,9 +1962,78 @@
mImpl.setRootViewData(visibleFrame, height);
}
- void copyRootViewBounds(@NonNull View view) {
- Rect visibleBounds = new Rect();
- view.getWindowVisibleDisplayFrame(visibleBounds);
- setRootViewData(visibleBounds, view.getHeight());
+ void copyRootViewBounds(@NonNull View rootView) {
+ Rect visibleInsets = getVisibleInsets(rootView);
+ int width = rootView.getWidth();
+ int height = rootView.getHeight();
+ if (visibleInsets == null) {
+ visibleInsets = new Rect(0, 0, width, height);
+ } else {
+ visibleInsets.set(visibleInsets.left, visibleInsets.top, width - visibleInsets.right,
+ height - visibleInsets.bottom);
+ }
+ setRootViewData(visibleInsets, rootView.getHeight());
+ }
+
+
+ /**
+ * Attempt to get a copy of the visible rect from this rootView's AttachInfo.
+ *
+ * @return a copy of the provided view's AttachInfo.mVisibleRect or null if anything fails
+ */
+ @Nullable
+ private Rect getVisibleInsets(@NonNull View rootView) {
+ if (!sVisibleRectReflectionFetched) {
+ loadReflectionField();
+ }
+
+ if (sGetViewRootImplMethod == null
+ || sAttachInfoClass == null
+ || sVisibleInsetsField == null) {
+ return null;
+ }
+
+ try {
+ Object viewRootImpl = sGetViewRootImplMethod.invoke(rootView);
+ if (viewRootImpl == null) {
+ Log.w(TAG, "Failed to get visible insets. getViewRootImpl() returned null from "
+ + "the provided view. This means that the view is either not "
+ + "attached or the method has been overridden",
+ new NullPointerException());
+ return null;
+ } else {
+ Object mAttachInfo = sAttachInfoField.get(viewRootImpl);
+ return new Rect((Rect) sVisibleInsetsField.get(mAttachInfo));
+ }
+ } catch (IllegalAccessException e) {
+ logReflectionError(e);
+ } catch (InvocationTargetException e) {
+ logReflectionError(e);
+ }
+ return null;
+ }
+
+ @SuppressLint("PrivateApi")
+ private static void loadReflectionField() {
+ try {
+ sGetViewRootImplMethod = View.class.getDeclaredMethod("getViewRootImpl");
+ sViewRootImplClass = Class.forName("android.view.ViewRootImpl");
+ sAttachInfoClass = Class.forName("android.view.View$AttachInfo");
+ sVisibleInsetsField = sAttachInfoClass.getDeclaredField("mVisibleInsets");
+ sAttachInfoField = sViewRootImplClass.getDeclaredField("mAttachInfo");
+ sVisibleInsetsField.setAccessible(true);
+ sAttachInfoField.setAccessible(true);
+ } catch (ClassNotFoundException e) {
+ logReflectionError(e);
+ } catch (NoSuchMethodException e) {
+ logReflectionError(e);
+ } catch (NoSuchFieldException e) {
+ logReflectionError(e);
+ }
+ sVisibleRectReflectionFetched = true;
+ }
+
+ private static void logReflectionError(Exception e) {
+ Log.e(TAG, "Failed to get visible insets. (Reflection error). " + e.getMessage(), e);
}
}
diff --git a/customview/customview/api/api_lint.ignore b/customview/customview/api/api_lint.ignore
index ba85783..1bd8a9c 100644
--- a/customview/customview/api/api_lint.ignore
+++ b/customview/customview/api/api_lint.ignore
@@ -1,4 +1,18 @@
// Baseline format: 1.0
+CallbackMethodName: androidx.customview.widget.ViewDragHelper.Callback#clampViewPositionHorizontal(android.view.View, int, int):
+ Callback method names must follow the on<Something> style: clampViewPositionHorizontal
+CallbackMethodName: androidx.customview.widget.ViewDragHelper.Callback#clampViewPositionVertical(android.view.View, int, int):
+ Callback method names must follow the on<Something> style: clampViewPositionVertical
+CallbackMethodName: androidx.customview.widget.ViewDragHelper.Callback#getOrderedChildIndex(int):
+ Callback method names must follow the on<Something> style: getOrderedChildIndex
+CallbackMethodName: androidx.customview.widget.ViewDragHelper.Callback#getViewHorizontalDragRange(android.view.View):
+ Callback method names must follow the on<Something> style: getViewHorizontalDragRange
+CallbackMethodName: androidx.customview.widget.ViewDragHelper.Callback#getViewVerticalDragRange(android.view.View):
+ Callback method names must follow the on<Something> style: getViewVerticalDragRange
+CallbackMethodName: androidx.customview.widget.ViewDragHelper.Callback#tryCaptureView(android.view.View, int):
+ Callback method names must follow the on<Something> style: tryCaptureView
+
+
MissingNullability: androidx.customview.view.AbsSavedState#CREATOR:
Missing nullability on field `CREATOR` in class `class androidx.customview.view.AbsSavedState`
MissingNullability: androidx.customview.view.AbsSavedState#EMPTY_STATE:
diff --git a/datastore/datastore-core/api/api_lint.ignore b/datastore/datastore-core/api/api_lint.ignore
index 8b7d447..f65d316 100644
--- a/datastore/datastore-core/api/api_lint.ignore
+++ b/datastore/datastore-core/api/api_lint.ignore
@@ -1,6 +1,4 @@
// Baseline format: 1.0
-MissingJvmstatic: androidx.datastore.migrations.SharedPreferencesMigration#SharedPreferencesMigration(android.content.Context, String, androidx.datastore.migrations.MigrationFromSharedPreferences<T>, java.util.Set<java.lang.String>, boolean):
- A Kotlin method with default parameter values should be annotated with @JvmOverloads for better Java interoperability; see https://android.github.io/kotlin-guides/interop.html#function-overloads-for-defaults
MissingJvmstatic: androidx.datastore.migrations.SharedPreferencesView#getString(String, String):
A Kotlin method with default parameter values should be annotated with @JvmOverloads for better Java interoperability; see https://android.github.io/kotlin-guides/interop.html#function-overloads-for-defaults
MissingJvmstatic: androidx.datastore.migrations.SharedPreferencesView#getStringSet(String, java.util.Set<java.lang.String>):
diff --git a/datastore/datastore-core/api/current.txt b/datastore/datastore-core/api/current.txt
index 80f8b50..34756f70 100644
--- a/datastore/datastore-core/api/current.txt
+++ b/datastore/datastore-core/api/current.txt
@@ -22,11 +22,15 @@
}
public final class DataStoreFactory {
- ctor public DataStoreFactory();
method public <T> androidx.datastore.DataStore<T> create(kotlin.jvm.functions.Function0<? extends java.io.File> produceFile, androidx.datastore.Serializer<T> serializer, androidx.datastore.handlers.ReplaceFileCorruptionHandler<T>? corruptionHandler = null, java.util.List<? extends androidx.datastore.DataMigration<T>> migrations = listOf(), kotlinx.coroutines.CoroutineScope scope = CoroutineScope(Dispatchers.IO + SupervisorJob()));
method public <T> androidx.datastore.DataStore<T> create(kotlin.jvm.functions.Function0<? extends java.io.File> produceFile, androidx.datastore.Serializer<T> serializer, androidx.datastore.handlers.ReplaceFileCorruptionHandler<T>? corruptionHandler = null, java.util.List<? extends androidx.datastore.DataMigration<T>> migrations = listOf());
method public <T> androidx.datastore.DataStore<T> create(kotlin.jvm.functions.Function0<? extends java.io.File> produceFile, androidx.datastore.Serializer<T> serializer, androidx.datastore.handlers.ReplaceFileCorruptionHandler<T>? corruptionHandler = null);
method public <T> androidx.datastore.DataStore<T> create(kotlin.jvm.functions.Function0<? extends java.io.File> produceFile, androidx.datastore.Serializer<T> serializer);
+ field public static final androidx.datastore.DataStoreFactory INSTANCE;
+ }
+
+ public final class DataStoreFactoryKt {
+ method public static <T> androidx.datastore.DataStore<T> createDataStore(android.content.Context, String fileName, androidx.datastore.Serializer<T> serializer, androidx.datastore.handlers.ReplaceFileCorruptionHandler<T>? corruptionHandler = null, java.util.List<? extends androidx.datastore.DataMigration<T>> migrations = listOf(), kotlinx.coroutines.CoroutineScope scope = CoroutineScope(Dispatchers.IO + SupervisorJob()));
}
public interface Serializer<T> {
diff --git a/datastore/datastore-core/api/public_plus_experimental_current.txt b/datastore/datastore-core/api/public_plus_experimental_current.txt
index 80f8b50..34756f70 100644
--- a/datastore/datastore-core/api/public_plus_experimental_current.txt
+++ b/datastore/datastore-core/api/public_plus_experimental_current.txt
@@ -22,11 +22,15 @@
}
public final class DataStoreFactory {
- ctor public DataStoreFactory();
method public <T> androidx.datastore.DataStore<T> create(kotlin.jvm.functions.Function0<? extends java.io.File> produceFile, androidx.datastore.Serializer<T> serializer, androidx.datastore.handlers.ReplaceFileCorruptionHandler<T>? corruptionHandler = null, java.util.List<? extends androidx.datastore.DataMigration<T>> migrations = listOf(), kotlinx.coroutines.CoroutineScope scope = CoroutineScope(Dispatchers.IO + SupervisorJob()));
method public <T> androidx.datastore.DataStore<T> create(kotlin.jvm.functions.Function0<? extends java.io.File> produceFile, androidx.datastore.Serializer<T> serializer, androidx.datastore.handlers.ReplaceFileCorruptionHandler<T>? corruptionHandler = null, java.util.List<? extends androidx.datastore.DataMigration<T>> migrations = listOf());
method public <T> androidx.datastore.DataStore<T> create(kotlin.jvm.functions.Function0<? extends java.io.File> produceFile, androidx.datastore.Serializer<T> serializer, androidx.datastore.handlers.ReplaceFileCorruptionHandler<T>? corruptionHandler = null);
method public <T> androidx.datastore.DataStore<T> create(kotlin.jvm.functions.Function0<? extends java.io.File> produceFile, androidx.datastore.Serializer<T> serializer);
+ field public static final androidx.datastore.DataStoreFactory INSTANCE;
+ }
+
+ public final class DataStoreFactoryKt {
+ method public static <T> androidx.datastore.DataStore<T> createDataStore(android.content.Context, String fileName, androidx.datastore.Serializer<T> serializer, androidx.datastore.handlers.ReplaceFileCorruptionHandler<T>? corruptionHandler = null, java.util.List<? extends androidx.datastore.DataMigration<T>> migrations = listOf(), kotlinx.coroutines.CoroutineScope scope = CoroutineScope(Dispatchers.IO + SupervisorJob()));
}
public interface Serializer<T> {
diff --git a/datastore/datastore-core/api/restricted_current.txt b/datastore/datastore-core/api/restricted_current.txt
index 80f8b50..34756f70 100644
--- a/datastore/datastore-core/api/restricted_current.txt
+++ b/datastore/datastore-core/api/restricted_current.txt
@@ -22,11 +22,15 @@
}
public final class DataStoreFactory {
- ctor public DataStoreFactory();
method public <T> androidx.datastore.DataStore<T> create(kotlin.jvm.functions.Function0<? extends java.io.File> produceFile, androidx.datastore.Serializer<T> serializer, androidx.datastore.handlers.ReplaceFileCorruptionHandler<T>? corruptionHandler = null, java.util.List<? extends androidx.datastore.DataMigration<T>> migrations = listOf(), kotlinx.coroutines.CoroutineScope scope = CoroutineScope(Dispatchers.IO + SupervisorJob()));
method public <T> androidx.datastore.DataStore<T> create(kotlin.jvm.functions.Function0<? extends java.io.File> produceFile, androidx.datastore.Serializer<T> serializer, androidx.datastore.handlers.ReplaceFileCorruptionHandler<T>? corruptionHandler = null, java.util.List<? extends androidx.datastore.DataMigration<T>> migrations = listOf());
method public <T> androidx.datastore.DataStore<T> create(kotlin.jvm.functions.Function0<? extends java.io.File> produceFile, androidx.datastore.Serializer<T> serializer, androidx.datastore.handlers.ReplaceFileCorruptionHandler<T>? corruptionHandler = null);
method public <T> androidx.datastore.DataStore<T> create(kotlin.jvm.functions.Function0<? extends java.io.File> produceFile, androidx.datastore.Serializer<T> serializer);
+ field public static final androidx.datastore.DataStoreFactory INSTANCE;
+ }
+
+ public final class DataStoreFactoryKt {
+ method public static <T> androidx.datastore.DataStore<T> createDataStore(android.content.Context, String fileName, androidx.datastore.Serializer<T> serializer, androidx.datastore.handlers.ReplaceFileCorruptionHandler<T>? corruptionHandler = null, java.util.List<? extends androidx.datastore.DataMigration<T>> migrations = listOf(), kotlinx.coroutines.CoroutineScope scope = CoroutineScope(Dispatchers.IO + SupervisorJob()));
}
public interface Serializer<T> {
diff --git a/datastore/datastore-core/src/test/java/androidx/datastore/DataStoreFactoryTest.kt b/datastore/datastore-core/src/androidTest/java/androidx/datastore/DataStoreFactoryTest.kt
similarity index 62%
rename from datastore/datastore-core/src/test/java/androidx/datastore/DataStoreFactoryTest.kt
rename to datastore/datastore-core/src/androidTest/java/androidx/datastore/DataStoreFactoryTest.kt
index f58247c..6ea9d01 100644
--- a/datastore/datastore-core/src/test/java/androidx/datastore/DataStoreFactoryTest.kt
+++ b/datastore/datastore-core/src/androidTest/java/androidx/datastore/DataStoreFactoryTest.kt
@@ -16,7 +16,9 @@
package androidx.datastore
+import android.content.Context
import androidx.datastore.handlers.ReplaceFileCorruptionHandler
+import androidx.test.core.app.ApplicationProvider
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.test.TestCoroutineScope
@@ -37,17 +39,18 @@
private lateinit var testFile: File
private lateinit var dataStoreScope: TestCoroutineScope
+ private lateinit var context: Context
@Before
fun setUp() {
testFile = tmp.newFile()
dataStoreScope = TestCoroutineScope()
+ context = ApplicationProvider.getApplicationContext()
}
@Test
fun testNewInstance() = runBlockingTest {
- val factory = DataStoreFactory()
- val store = factory.create(
+ val store = DataStoreFactory.create(
produceFile = { testFile },
serializer = TestingSerializer(),
scope = dataStoreScope
@@ -65,9 +68,7 @@
fun testCorruptionHandlerInstalled() = runBlockingTest {
val valueToReplace = 123.toByte()
- val factory = DataStoreFactory()
-
- val store = factory.create(
+ val store = DataStoreFactory.create(
produceFile = { testFile },
serializer = TestingSerializer(failReadWithCorruptionException = true),
corruptionHandler = ReplaceFileCorruptionHandler<Byte> {
@@ -81,24 +82,22 @@
@Test
fun testMigrationsInstalled() = runBlockingTest {
- val factory = DataStoreFactory()
-
val migratedByte = 1
val migratePlus2 = object : DataMigration<Byte> {
- override suspend fun shouldMigrate(currentData: Byte) = true
- override suspend fun migrate(currentData: Byte) = currentData.inc().inc()
- override suspend fun cleanUp() {}
- }
+ override suspend fun shouldMigrate(currentData: Byte) = true
+ override suspend fun migrate(currentData: Byte) = currentData.inc().inc()
+ override suspend fun cleanUp() {}
+ }
val migrateMinus1 = object : DataMigration<Byte> {
- override suspend fun shouldMigrate(currentData: Byte) = true
+ override suspend fun shouldMigrate(currentData: Byte) = true
- override suspend fun migrate(currentData: Byte) = currentData.dec()
+ override suspend fun migrate(currentData: Byte) = currentData.dec()
- override suspend fun cleanUp() {}
- }
+ override suspend fun cleanUp() {}
+ }
- val store = factory.create(
+ val store = DataStoreFactory.create(
produceFile = { testFile },
migrations = listOf(migratePlus2, migrateMinus1),
scope = dataStoreScope,
@@ -107,4 +106,33 @@
assertThat(store.data.first()).isEqualTo(migratedByte)
}
+
+ @Test
+ fun testCreateWithContextAndName() = runBlockingTest {
+ val byte = 1
+
+ var store = context.createDataStore(
+ serializer = TestingSerializer(),
+ fileName = "my_settings.byte",
+ scope = dataStoreScope
+ )
+ store.updateData { 1 }
+
+ // Create it again and confirm it's still there
+ store = context.createDataStore(
+ serializer = TestingSerializer(),
+ fileName = "my_settings.byte",
+ scope = dataStoreScope
+ )
+ assertThat(store.data.first()).isEqualTo(byte)
+
+ // Check that the file name is context.filesDir + fileName
+ store = DataStoreFactory.create(
+ produceFile = {
+ File(context.filesDir, "datastore/my_settings.byte")
+ }, serializer = TestingSerializer(),
+ scope = dataStoreScope
+ )
+ assertThat(store.data.first()).isEqualTo(byte)
+ }
}
\ No newline at end of file
diff --git a/datastore/datastore-core/src/androidTest/java/migrations/SharedPreferencesMigrationTest.kt b/datastore/datastore-core/src/androidTest/java/androidx/datastore/migrations/SharedPreferencesMigrationTest.kt
similarity index 98%
rename from datastore/datastore-core/src/androidTest/java/migrations/SharedPreferencesMigrationTest.kt
rename to datastore/datastore-core/src/androidTest/java/androidx/datastore/migrations/SharedPreferencesMigrationTest.kt
index bb90413..73501f4 100644
--- a/datastore/datastore-core/src/androidTest/java/migrations/SharedPreferencesMigrationTest.kt
+++ b/datastore/datastore-core/src/androidTest/java/androidx/datastore/migrations/SharedPreferencesMigrationTest.kt
@@ -138,7 +138,7 @@
private fun getDataStoreWithMigrations(
migrations: List<DataMigration<Byte>>
): DataStore<Byte> {
- return DataStoreFactory().create(
+ return DataStoreFactory.create(
produceFile = { datastoreFile },
serializer = TestingSerializer(),
migrations = migrations,
diff --git a/datastore/datastore-core/src/main/java/androidx/datastore/DataStoreFactory.kt b/datastore/datastore-core/src/main/java/androidx/datastore/DataStoreFactory.kt
index 969495e..496e239 100644
--- a/datastore/datastore-core/src/main/java/androidx/datastore/DataStoreFactory.kt
+++ b/datastore/datastore-core/src/main/java/androidx/datastore/DataStoreFactory.kt
@@ -16,6 +16,7 @@
package androidx.datastore
+import android.content.Context
import androidx.datastore.handlers.NoOpCorruptionHandler
import androidx.datastore.handlers.ReplaceFileCorruptionHandler
import kotlinx.coroutines.CoroutineScope
@@ -26,7 +27,7 @@
/**
* Public factory for creating DataStore instances.
*/
-class DataStoreFactory {
+object DataStoreFactory {
/**
* Create an instance of SingleProcessDataStore. The user is responsible for ensuring that
* there is never more than one DataStore acting on a file at a time.
@@ -63,4 +64,34 @@
initTasksList = listOf(DataMigrationInitializer.getInitializer(migrations)),
scope = scope
)
-}
\ No newline at end of file
+}
+
+/**
+ * Create an instance of SingleProcessDataStore. The user is responsible for ensuring that
+ * there is never more than one instance of SingleProcessDataStore acting on a file at a time.
+ *
+ * @param fileName the filename relative to Context.filesDir that DataStore acts on. The File is
+ * obtained by calling File(context.filesDir, fileName). No two instances of DataStore should
+ * act on the same file at the same time.
+ * @param corruptionHandler The corruptionHandler is invoked if DataStore encounters a
+ * [CorruptionException] when attempting to read data. CorruptionExceptions are thrown by
+ * serializers when data can not be de-serialized.
+ * @param migrations are run before any access to data can occur. Each producer and migration
+ * may be run more than once whether or not it already succeeded (potentially because another
+ * migration failed or a write to disk failed.)
+ * @param scope The scope in which IO operations and transform functions will execute.
+ */
+fun <T> Context.createDataStore(
+ fileName: String,
+ serializer: Serializer<T>,
+ corruptionHandler: ReplaceFileCorruptionHandler<T>? = null,
+ migrations: List<DataMigration<T>> = listOf(),
+ scope: CoroutineScope = CoroutineScope(Dispatchers.IO + SupervisorJob())
+): DataStore<T> =
+ DataStoreFactory.create(
+ produceFile = { File(this.filesDir, "datastore/$fileName") },
+ serializer = serializer,
+ corruptionHandler = corruptionHandler,
+ migrations = migrations,
+ scope = scope
+ )
diff --git a/datastore/datastore-preferences/api/api_lint.ignore b/datastore/datastore-preferences/api/api_lint.ignore
index 50dbca8..e064eef 100644
--- a/datastore/datastore-preferences/api/api_lint.ignore
+++ b/datastore/datastore-preferences/api/api_lint.ignore
@@ -1,7 +1,9 @@
// Baseline format: 1.0
-BuilderSetStyle: androidx.datastore.preferences.Preferences.Builder#remove(String):
- Builder methods names should use setFoo() / addFoo() / clearFoo() style: method androidx.datastore.preferences.Preferences.Builder.remove(String)
-
-
-MissingJvmstatic: androidx.datastore.preferences.SharedPreferencesToPreferencesKt#SharedPreferencesMigration(android.content.Context, String, java.util.Set<java.lang.String>, boolean):
+MissingJvmstatic: androidx.datastore.preferences.PreferenceDataStoreFactory#create(kotlin.jvm.functions.Function0<? extends java.io.File>, androidx.datastore.handlers.ReplaceFileCorruptionHandler<androidx.datastore.preferences.Preferences>, java.util.List<? extends androidx.datastore.DataMigration<androidx.datastore.preferences.Preferences>>, kotlinx.coroutines.CoroutineScope):
A Kotlin method with default parameter values should be annotated with @JvmOverloads for better Java interoperability; see https://android.github.io/kotlin-guides/interop.html#function-overloads-for-defaults
+
+
+MissingNullability: androidx.datastore.preferences.PreferencesKt#preferencesKey(String):
+ Missing nullability on method `preferencesKey` return
+MissingNullability: androidx.datastore.preferences.PreferencesKt#preferencesSetKey(String):
+ Missing nullability on method `preferencesSetKey` return
diff --git a/datastore/datastore-preferences/api/current.txt b/datastore/datastore-preferences/api/current.txt
index 217726b..8039a28 100644
--- a/datastore/datastore-preferences/api/current.txt
+++ b/datastore/datastore-preferences/api/current.txt
@@ -1,43 +1,51 @@
// Signature format: 3.0
package androidx.datastore.preferences {
+ public final class MutablePreferences extends androidx.datastore.preferences.Preferences {
+ method public java.util.Map<androidx.datastore.preferences.Preferences.Key<?>,java.lang.Object> asMap();
+ method public operator <T> boolean contains(androidx.datastore.preferences.Preferences.Key<T> key);
+ method public operator <T> T? get(androidx.datastore.preferences.Preferences.Key<T> key);
+ method public operator <T> void set(androidx.datastore.preferences.Preferences.Key<T> key, T? value);
+ }
+
public final class PreferenceDataStoreFactory {
- ctor public PreferenceDataStoreFactory();
method public androidx.datastore.DataStore<androidx.datastore.preferences.Preferences> create(kotlin.jvm.functions.Function0<? extends java.io.File> produceFile, androidx.datastore.handlers.ReplaceFileCorruptionHandler<androidx.datastore.preferences.Preferences>? corruptionHandler = null, java.util.List<? extends androidx.datastore.DataMigration<androidx.datastore.preferences.Preferences>> migrations = listOf(), kotlinx.coroutines.CoroutineScope scope = CoroutineScope(Dispatchers.IO + SupervisorJob()));
- method public androidx.datastore.DataStore<androidx.datastore.preferences.Preferences> create(kotlin.jvm.functions.Function0<? extends java.io.File> produceFile, androidx.datastore.handlers.ReplaceFileCorruptionHandler<androidx.datastore.preferences.Preferences>? corruptionHandler = null, java.util.List<? extends androidx.datastore.DataMigration<androidx.datastore.preferences.Preferences>> migrations = listOf());
- method public androidx.datastore.DataStore<androidx.datastore.preferences.Preferences> create(kotlin.jvm.functions.Function0<? extends java.io.File> produceFile, androidx.datastore.handlers.ReplaceFileCorruptionHandler<androidx.datastore.preferences.Preferences>? corruptionHandler = null);
- method public androidx.datastore.DataStore<androidx.datastore.preferences.Preferences> create(kotlin.jvm.functions.Function0<? extends java.io.File> produceFile);
+ field public static final androidx.datastore.preferences.PreferenceDataStoreFactory INSTANCE;
}
- public final class Preferences {
- method public operator boolean contains(String key);
- method public static androidx.datastore.preferences.Preferences empty();
- method public java.util.Map<java.lang.String,java.lang.Object> getAll();
- method public boolean getBoolean(String key, boolean defaultValue);
- method public float getFloat(String key, float defaultValue);
- method public int getInt(String key, int defaultValue);
- method public long getLong(String key, long defaultValue);
- method public String getString(String key, String defaultValue);
- method public java.util.Set<java.lang.String> getStringSet(String key, java.util.Set<java.lang.String> defaultValue);
- method public androidx.datastore.preferences.Preferences.Builder toBuilder();
- field public static final androidx.datastore.preferences.Preferences.Companion Companion;
+ public final class PreferenceDataStoreFactoryKt {
+ method public static androidx.datastore.DataStore<androidx.datastore.preferences.Preferences> createDataStore(android.content.Context, String name, androidx.datastore.handlers.ReplaceFileCorruptionHandler<androidx.datastore.preferences.Preferences>? corruptionHandler = null, java.util.List<? extends androidx.datastore.DataMigration<androidx.datastore.preferences.Preferences>> migrations = listOf(), kotlinx.coroutines.CoroutineScope scope = CoroutineScope(Dispatchers.IO + SupervisorJob()));
}
- public static final class Preferences.Builder {
- ctor public Preferences.Builder();
- method public androidx.datastore.preferences.Preferences build();
- method public androidx.datastore.preferences.Preferences.Builder clear();
- method public androidx.datastore.preferences.Preferences.Builder remove(String key);
- method public androidx.datastore.preferences.Preferences.Builder setBoolean(String key, boolean newValue);
- method public androidx.datastore.preferences.Preferences.Builder setFloat(String key, float newValue);
- method public androidx.datastore.preferences.Preferences.Builder setInt(String key, int newValue);
- method public androidx.datastore.preferences.Preferences.Builder setLong(String key, long newValue);
- method public androidx.datastore.preferences.Preferences.Builder setString(String key, String newValue);
- method public androidx.datastore.preferences.Preferences.Builder setStringSet(String key, java.util.Set<java.lang.String> newValue);
+ public abstract class Preferences {
+ method public abstract java.util.Map<androidx.datastore.preferences.Preferences.Key<?>,java.lang.Object> asMap();
+ method public abstract operator <T> boolean contains(androidx.datastore.preferences.Preferences.Key<T> key);
+ method public abstract operator <T> T? get(androidx.datastore.preferences.Preferences.Key<T> key);
}
- public static final class Preferences.Companion {
- method public androidx.datastore.preferences.Preferences empty();
+ public static final class Preferences.Key<T> {
+ method public String getName();
+ }
+
+ public static final class Preferences.Pair<T> {
+ }
+
+ public final class PreferencesKt {
+ method public static void clear(androidx.datastore.preferences.MutablePreferences);
+ method public static suspend Object? edit(androidx.datastore.DataStore<androidx.datastore.preferences.Preferences>, kotlin.jvm.functions.Function2<? super androidx.datastore.preferences.MutablePreferences,? super kotlin.coroutines.Continuation<? super kotlin.Unit>,?> transform, kotlin.coroutines.Continuation<? super androidx.datastore.preferences.Preferences> p);
+ method public static androidx.datastore.preferences.Preferences emptyPreferences();
+ method public static operator void minusAssign(androidx.datastore.preferences.MutablePreferences, androidx.datastore.preferences.Preferences.Key<?> key);
+ method public static androidx.datastore.preferences.MutablePreferences mutablePreferencesOf(androidx.datastore.preferences.Preferences.Pair<?>... pairs);
+ method public static operator void plusAssign(androidx.datastore.preferences.MutablePreferences, androidx.datastore.preferences.Preferences prefs);
+ method public static operator void plusAssign(androidx.datastore.preferences.MutablePreferences, androidx.datastore.preferences.Preferences.Pair<?> pair);
+ method public static inline <reified T> androidx.datastore.preferences.Preferences.Key<T>! preferencesKey(String name);
+ method public static androidx.datastore.preferences.Preferences preferencesOf(androidx.datastore.preferences.Preferences.Pair<?>... pairs);
+ method public static inline <reified T> androidx.datastore.preferences.Preferences.Key<java.util.Set<? extends T>>! preferencesSetKey(String name);
+ method public static void putAll(androidx.datastore.preferences.MutablePreferences, androidx.datastore.preferences.Preferences.Pair<?>... pairs);
+ method public static <T> T! remove(androidx.datastore.preferences.MutablePreferences, androidx.datastore.preferences.Preferences.Key<T> key);
+ method public static infix <T> androidx.datastore.preferences.Preferences.Pair<T> to(androidx.datastore.preferences.Preferences.Key<T>, T? value);
+ method public static androidx.datastore.preferences.MutablePreferences toMutablePreferences(androidx.datastore.preferences.Preferences);
+ method public static androidx.datastore.preferences.Preferences toPreferences(androidx.datastore.preferences.Preferences);
}
public final class SharedPreferencesMigrationKt {
diff --git a/datastore/datastore-preferences/api/public_plus_experimental_current.txt b/datastore/datastore-preferences/api/public_plus_experimental_current.txt
index 217726b..8039a28 100644
--- a/datastore/datastore-preferences/api/public_plus_experimental_current.txt
+++ b/datastore/datastore-preferences/api/public_plus_experimental_current.txt
@@ -1,43 +1,51 @@
// Signature format: 3.0
package androidx.datastore.preferences {
+ public final class MutablePreferences extends androidx.datastore.preferences.Preferences {
+ method public java.util.Map<androidx.datastore.preferences.Preferences.Key<?>,java.lang.Object> asMap();
+ method public operator <T> boolean contains(androidx.datastore.preferences.Preferences.Key<T> key);
+ method public operator <T> T? get(androidx.datastore.preferences.Preferences.Key<T> key);
+ method public operator <T> void set(androidx.datastore.preferences.Preferences.Key<T> key, T? value);
+ }
+
public final class PreferenceDataStoreFactory {
- ctor public PreferenceDataStoreFactory();
method public androidx.datastore.DataStore<androidx.datastore.preferences.Preferences> create(kotlin.jvm.functions.Function0<? extends java.io.File> produceFile, androidx.datastore.handlers.ReplaceFileCorruptionHandler<androidx.datastore.preferences.Preferences>? corruptionHandler = null, java.util.List<? extends androidx.datastore.DataMigration<androidx.datastore.preferences.Preferences>> migrations = listOf(), kotlinx.coroutines.CoroutineScope scope = CoroutineScope(Dispatchers.IO + SupervisorJob()));
- method public androidx.datastore.DataStore<androidx.datastore.preferences.Preferences> create(kotlin.jvm.functions.Function0<? extends java.io.File> produceFile, androidx.datastore.handlers.ReplaceFileCorruptionHandler<androidx.datastore.preferences.Preferences>? corruptionHandler = null, java.util.List<? extends androidx.datastore.DataMigration<androidx.datastore.preferences.Preferences>> migrations = listOf());
- method public androidx.datastore.DataStore<androidx.datastore.preferences.Preferences> create(kotlin.jvm.functions.Function0<? extends java.io.File> produceFile, androidx.datastore.handlers.ReplaceFileCorruptionHandler<androidx.datastore.preferences.Preferences>? corruptionHandler = null);
- method public androidx.datastore.DataStore<androidx.datastore.preferences.Preferences> create(kotlin.jvm.functions.Function0<? extends java.io.File> produceFile);
+ field public static final androidx.datastore.preferences.PreferenceDataStoreFactory INSTANCE;
}
- public final class Preferences {
- method public operator boolean contains(String key);
- method public static androidx.datastore.preferences.Preferences empty();
- method public java.util.Map<java.lang.String,java.lang.Object> getAll();
- method public boolean getBoolean(String key, boolean defaultValue);
- method public float getFloat(String key, float defaultValue);
- method public int getInt(String key, int defaultValue);
- method public long getLong(String key, long defaultValue);
- method public String getString(String key, String defaultValue);
- method public java.util.Set<java.lang.String> getStringSet(String key, java.util.Set<java.lang.String> defaultValue);
- method public androidx.datastore.preferences.Preferences.Builder toBuilder();
- field public static final androidx.datastore.preferences.Preferences.Companion Companion;
+ public final class PreferenceDataStoreFactoryKt {
+ method public static androidx.datastore.DataStore<androidx.datastore.preferences.Preferences> createDataStore(android.content.Context, String name, androidx.datastore.handlers.ReplaceFileCorruptionHandler<androidx.datastore.preferences.Preferences>? corruptionHandler = null, java.util.List<? extends androidx.datastore.DataMigration<androidx.datastore.preferences.Preferences>> migrations = listOf(), kotlinx.coroutines.CoroutineScope scope = CoroutineScope(Dispatchers.IO + SupervisorJob()));
}
- public static final class Preferences.Builder {
- ctor public Preferences.Builder();
- method public androidx.datastore.preferences.Preferences build();
- method public androidx.datastore.preferences.Preferences.Builder clear();
- method public androidx.datastore.preferences.Preferences.Builder remove(String key);
- method public androidx.datastore.preferences.Preferences.Builder setBoolean(String key, boolean newValue);
- method public androidx.datastore.preferences.Preferences.Builder setFloat(String key, float newValue);
- method public androidx.datastore.preferences.Preferences.Builder setInt(String key, int newValue);
- method public androidx.datastore.preferences.Preferences.Builder setLong(String key, long newValue);
- method public androidx.datastore.preferences.Preferences.Builder setString(String key, String newValue);
- method public androidx.datastore.preferences.Preferences.Builder setStringSet(String key, java.util.Set<java.lang.String> newValue);
+ public abstract class Preferences {
+ method public abstract java.util.Map<androidx.datastore.preferences.Preferences.Key<?>,java.lang.Object> asMap();
+ method public abstract operator <T> boolean contains(androidx.datastore.preferences.Preferences.Key<T> key);
+ method public abstract operator <T> T? get(androidx.datastore.preferences.Preferences.Key<T> key);
}
- public static final class Preferences.Companion {
- method public androidx.datastore.preferences.Preferences empty();
+ public static final class Preferences.Key<T> {
+ method public String getName();
+ }
+
+ public static final class Preferences.Pair<T> {
+ }
+
+ public final class PreferencesKt {
+ method public static void clear(androidx.datastore.preferences.MutablePreferences);
+ method public static suspend Object? edit(androidx.datastore.DataStore<androidx.datastore.preferences.Preferences>, kotlin.jvm.functions.Function2<? super androidx.datastore.preferences.MutablePreferences,? super kotlin.coroutines.Continuation<? super kotlin.Unit>,?> transform, kotlin.coroutines.Continuation<? super androidx.datastore.preferences.Preferences> p);
+ method public static androidx.datastore.preferences.Preferences emptyPreferences();
+ method public static operator void minusAssign(androidx.datastore.preferences.MutablePreferences, androidx.datastore.preferences.Preferences.Key<?> key);
+ method public static androidx.datastore.preferences.MutablePreferences mutablePreferencesOf(androidx.datastore.preferences.Preferences.Pair<?>... pairs);
+ method public static operator void plusAssign(androidx.datastore.preferences.MutablePreferences, androidx.datastore.preferences.Preferences prefs);
+ method public static operator void plusAssign(androidx.datastore.preferences.MutablePreferences, androidx.datastore.preferences.Preferences.Pair<?> pair);
+ method public static inline <reified T> androidx.datastore.preferences.Preferences.Key<T>! preferencesKey(String name);
+ method public static androidx.datastore.preferences.Preferences preferencesOf(androidx.datastore.preferences.Preferences.Pair<?>... pairs);
+ method public static inline <reified T> androidx.datastore.preferences.Preferences.Key<java.util.Set<? extends T>>! preferencesSetKey(String name);
+ method public static void putAll(androidx.datastore.preferences.MutablePreferences, androidx.datastore.preferences.Preferences.Pair<?>... pairs);
+ method public static <T> T! remove(androidx.datastore.preferences.MutablePreferences, androidx.datastore.preferences.Preferences.Key<T> key);
+ method public static infix <T> androidx.datastore.preferences.Preferences.Pair<T> to(androidx.datastore.preferences.Preferences.Key<T>, T? value);
+ method public static androidx.datastore.preferences.MutablePreferences toMutablePreferences(androidx.datastore.preferences.Preferences);
+ method public static androidx.datastore.preferences.Preferences toPreferences(androidx.datastore.preferences.Preferences);
}
public final class SharedPreferencesMigrationKt {
diff --git a/datastore/datastore-preferences/api/restricted_current.txt b/datastore/datastore-preferences/api/restricted_current.txt
index 217726b..14b8639 100644
--- a/datastore/datastore-preferences/api/restricted_current.txt
+++ b/datastore/datastore-preferences/api/restricted_current.txt
@@ -1,43 +1,52 @@
// Signature format: 3.0
package androidx.datastore.preferences {
+ public final class MutablePreferences extends androidx.datastore.preferences.Preferences {
+ method public java.util.Map<androidx.datastore.preferences.Preferences.Key<?>,java.lang.Object> asMap();
+ method public operator <T> boolean contains(androidx.datastore.preferences.Preferences.Key<T> key);
+ method public operator <T> T? get(androidx.datastore.preferences.Preferences.Key<T> key);
+ method public operator <T> void set(androidx.datastore.preferences.Preferences.Key<T> key, T? value);
+ }
+
public final class PreferenceDataStoreFactory {
- ctor public PreferenceDataStoreFactory();
method public androidx.datastore.DataStore<androidx.datastore.preferences.Preferences> create(kotlin.jvm.functions.Function0<? extends java.io.File> produceFile, androidx.datastore.handlers.ReplaceFileCorruptionHandler<androidx.datastore.preferences.Preferences>? corruptionHandler = null, java.util.List<? extends androidx.datastore.DataMigration<androidx.datastore.preferences.Preferences>> migrations = listOf(), kotlinx.coroutines.CoroutineScope scope = CoroutineScope(Dispatchers.IO + SupervisorJob()));
- method public androidx.datastore.DataStore<androidx.datastore.preferences.Preferences> create(kotlin.jvm.functions.Function0<? extends java.io.File> produceFile, androidx.datastore.handlers.ReplaceFileCorruptionHandler<androidx.datastore.preferences.Preferences>? corruptionHandler = null, java.util.List<? extends androidx.datastore.DataMigration<androidx.datastore.preferences.Preferences>> migrations = listOf());
- method public androidx.datastore.DataStore<androidx.datastore.preferences.Preferences> create(kotlin.jvm.functions.Function0<? extends java.io.File> produceFile, androidx.datastore.handlers.ReplaceFileCorruptionHandler<androidx.datastore.preferences.Preferences>? corruptionHandler = null);
- method public androidx.datastore.DataStore<androidx.datastore.preferences.Preferences> create(kotlin.jvm.functions.Function0<? extends java.io.File> produceFile);
+ field public static final androidx.datastore.preferences.PreferenceDataStoreFactory INSTANCE;
}
- public final class Preferences {
- method public operator boolean contains(String key);
- method public static androidx.datastore.preferences.Preferences empty();
- method public java.util.Map<java.lang.String,java.lang.Object> getAll();
- method public boolean getBoolean(String key, boolean defaultValue);
- method public float getFloat(String key, float defaultValue);
- method public int getInt(String key, int defaultValue);
- method public long getLong(String key, long defaultValue);
- method public String getString(String key, String defaultValue);
- method public java.util.Set<java.lang.String> getStringSet(String key, java.util.Set<java.lang.String> defaultValue);
- method public androidx.datastore.preferences.Preferences.Builder toBuilder();
- field public static final androidx.datastore.preferences.Preferences.Companion Companion;
+ public final class PreferenceDataStoreFactoryKt {
+ method public static androidx.datastore.DataStore<androidx.datastore.preferences.Preferences> createDataStore(android.content.Context, String name, androidx.datastore.handlers.ReplaceFileCorruptionHandler<androidx.datastore.preferences.Preferences>? corruptionHandler = null, java.util.List<? extends androidx.datastore.DataMigration<androidx.datastore.preferences.Preferences>> migrations = listOf(), kotlinx.coroutines.CoroutineScope scope = CoroutineScope(Dispatchers.IO + SupervisorJob()));
}
- public static final class Preferences.Builder {
- ctor public Preferences.Builder();
- method public androidx.datastore.preferences.Preferences build();
- method public androidx.datastore.preferences.Preferences.Builder clear();
- method public androidx.datastore.preferences.Preferences.Builder remove(String key);
- method public androidx.datastore.preferences.Preferences.Builder setBoolean(String key, boolean newValue);
- method public androidx.datastore.preferences.Preferences.Builder setFloat(String key, float newValue);
- method public androidx.datastore.preferences.Preferences.Builder setInt(String key, int newValue);
- method public androidx.datastore.preferences.Preferences.Builder setLong(String key, long newValue);
- method public androidx.datastore.preferences.Preferences.Builder setString(String key, String newValue);
- method public androidx.datastore.preferences.Preferences.Builder setStringSet(String key, java.util.Set<java.lang.String> newValue);
+ public abstract class Preferences {
+ method public abstract java.util.Map<androidx.datastore.preferences.Preferences.Key<?>,java.lang.Object> asMap();
+ method public abstract operator <T> boolean contains(androidx.datastore.preferences.Preferences.Key<T> key);
+ method public abstract operator <T> T? get(androidx.datastore.preferences.Preferences.Key<T> key);
}
- public static final class Preferences.Companion {
- method public androidx.datastore.preferences.Preferences empty();
+ public static final class Preferences.Key<T> {
+ ctor @kotlin.PublishedApi internal Preferences.Key(String name);
+ method public String getName();
+ }
+
+ public static final class Preferences.Pair<T> {
+ }
+
+ public final class PreferencesKt {
+ method public static void clear(androidx.datastore.preferences.MutablePreferences);
+ method public static suspend Object? edit(androidx.datastore.DataStore<androidx.datastore.preferences.Preferences>, kotlin.jvm.functions.Function2<? super androidx.datastore.preferences.MutablePreferences,? super kotlin.coroutines.Continuation<? super kotlin.Unit>,?> transform, kotlin.coroutines.Continuation<? super androidx.datastore.preferences.Preferences> p);
+ method public static androidx.datastore.preferences.Preferences emptyPreferences();
+ method public static operator void minusAssign(androidx.datastore.preferences.MutablePreferences, androidx.datastore.preferences.Preferences.Key<?> key);
+ method public static androidx.datastore.preferences.MutablePreferences mutablePreferencesOf(androidx.datastore.preferences.Preferences.Pair<?>... pairs);
+ method public static operator void plusAssign(androidx.datastore.preferences.MutablePreferences, androidx.datastore.preferences.Preferences prefs);
+ method public static operator void plusAssign(androidx.datastore.preferences.MutablePreferences, androidx.datastore.preferences.Preferences.Pair<?> pair);
+ method public static inline <reified T> androidx.datastore.preferences.Preferences.Key<T>! preferencesKey(String name);
+ method public static androidx.datastore.preferences.Preferences preferencesOf(androidx.datastore.preferences.Preferences.Pair<?>... pairs);
+ method public static inline <reified T> androidx.datastore.preferences.Preferences.Key<java.util.Set<? extends T>>! preferencesSetKey(String name);
+ method public static void putAll(androidx.datastore.preferences.MutablePreferences, androidx.datastore.preferences.Preferences.Pair<?>... pairs);
+ method public static <T> T! remove(androidx.datastore.preferences.MutablePreferences, androidx.datastore.preferences.Preferences.Key<T> key);
+ method public static infix <T> androidx.datastore.preferences.Preferences.Pair<T> to(androidx.datastore.preferences.Preferences.Key<T>, T? value);
+ method public static androidx.datastore.preferences.MutablePreferences toMutablePreferences(androidx.datastore.preferences.Preferences);
+ method public static androidx.datastore.preferences.Preferences toPreferences(androidx.datastore.preferences.Preferences);
}
public final class SharedPreferencesMigrationKt {
diff --git a/datastore/datastore-preferences/build.gradle b/datastore/datastore-preferences/build.gradle
index 4954606..a4ba37e 100644
--- a/datastore/datastore-preferences/build.gradle
+++ b/datastore/datastore-preferences/build.gradle
@@ -16,22 +16,39 @@
import static androidx.build.dependencies.DependenciesKt.*
import androidx.build.LibraryGroups
-import androidx.build.AndroidXExtension
import androidx.build.Publish
+buildscript {
+ dependencies {
+ classpath JARJAR
+ }
+}
+
plugins {
id("AndroidXPlugin")
id("com.android.library")
id("kotlin-android")
- id("com.google.protobuf")
+ id("org.anarres.jarjar")
+}
+
+
+// Include :datastore-preferences-proto
+android.libraryVariants.all { variant ->
+ def variantName = variant.name
+ def suffix = variantName.capitalize()
+ def jarjarConfigName = "jarjar${suffix}"
+ def jarjarConf = configurations.register(jarjarConfigName)
+ // Treat datastore-preferences-proto as a local jar and package it inside the aar
+ dependencies.add(jarjarConfigName, project.dependencies.project(
+ path: ":datastore:datastore-preferences:datastore-preferences-proto",
+ configuration: jarjarConfigName))
+ dependencies.add("${variantName}Implementation", files(jarjarConf))
}
dependencies {
api(KOTLIN_STDLIB)
api(project(":datastore:datastore-core"))
- implementation(PROTOBUF_LITE)
-
testImplementation(JUNIT)
testImplementation(KOTLIN_COROUTINES_TEST)
testImplementation(KOTLIN_TEST)
@@ -43,26 +60,6 @@
androidTestImplementation(ANDROIDX_TEST_CORE)
}
-protobuf {
- protoc {
- artifact = PROTOBUF_COMPILER
- }
-
- // Generates the java proto-lite code for the protos in this project. See
- // https://github.com/google/protobuf-gradle-plugin#customizing-protobuf-compilation
- // for more information.
- generateProtoTasks {
- all().each { task ->
- task.builtins {
- java {
- option 'lite'
- }
- }
- }
- }
-}
-
-
androidx {
name = "Android Preferences DataStore"
publish = Publish.SNAPSHOT_AND_RELEASE
diff --git a/datastore/datastore-preferences/datastore-preferences-proto/build.gradle b/datastore/datastore-preferences/datastore-preferences-proto/build.gradle
new file mode 100644
index 0000000..d6e367d
--- /dev/null
+++ b/datastore/datastore-preferences/datastore-preferences-proto/build.gradle
@@ -0,0 +1,118 @@
+/*
+ * Copyright 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import org.anarres.gradle.plugin.jarjar.JarjarTask
+
+import static androidx.build.dependencies.DependenciesKt.*
+import androidx.build.LibraryGroups
+import androidx.build.Publish
+
+
+buildscript {
+ dependencies {
+ classpath 'org.anarres.jarjar:jarjar-gradle:1.0.1'
+ }
+}
+
+
+plugins {
+ id("AndroidXPlugin")
+ id("com.android.library")
+ id("kotlin-android")
+ id("com.google.protobuf")
+}
+
+apply plugin: 'org.anarres.jarjar'
+
+
+dependencies {
+ implementation(PROTOBUF_LITE)
+ api(project(":datastore:datastore-core"))
+}
+
+protobuf {
+ protoc {
+ artifact = PROTOBUF_COMPILER
+ }
+
+ // Generates the java proto-lite code for the protos in this project. See
+ // https://github.com/google/protobuf-gradle-plugin#customizing-protobuf-compilation
+ // for more information.
+ generateProtoTasks {
+ all().each { task ->
+ task.builtins {
+ java {
+ option 'lite'
+ }
+ }
+ }
+ }
+}
+
+android.libraryVariants.all { variant ->
+ def variantName = variant.name
+ def suffix = variantName.capitalize()
+ def jarjarTask = tasks.register("jarjar${suffix}", JarjarTask) {
+ destinationName "datastore-preferences-proto-${variantName}-jarjar.jar"
+ dependsOn protoLiteJarWithoutProtoFiles
+ from(files(protoLiteJarWithoutProtoFiles.archiveFile.get().getAsFile()))
+
+ from files(variant.javaCompileProvider.get().destinationDir)
+ dependsOn variant.javaCompileProvider.get()
+
+ if (suffix == "Debug") {
+ from(files(compileDebugKotlin.outputs.files))
+ } else if (suffix == "Release") {
+ from(files(compileReleaseKotlin.outputs.files))
+ } else {
+ throw IllegalStateException("Expected either debug or release variant.")
+ }
+ classRename 'com.google.protobuf.**', 'androidx.datastore.preferences.protobuf.@1'
+ }
+
+ def jarjarConf = configurations.register("jarjar${suffix}")
+ artifacts.add("${jarjarConf.name}", jarjarTask.get().destinationPath) {
+ name "datastore-preferences-proto-${variantName}-jarjar"
+ type 'jar'
+ builtBy jarjarTask
+ }
+}
+
+// The proto-lite dependency includes .proto files, which are not used by icing. When apps depend on
+// appsearch as well as proto-lite directly, these files conflict since jarjar only renames the java
+// classes. Remove them here since they are unused.
+tasks.register("protoLiteJarWithoutProtoFiles", Jar) {
+ // Get proto lite jar as a file.
+ def jarFile = configurations.detachedConfiguration(dependencies.create
+ (PROTOBUF_LITE)).getSingleFile()
+
+ // Expand the jar and remove any .proto files.
+ from(zipTree(jarFile)) {
+ exclude("**/*.proto")
+ }
+
+
+ into 'datastore-preferences-proto-lite-dep'
+}
+
+androidx {
+ name = "Android Preferences DataStore Proto"
+ publish = Publish.NONE
+ mavenGroup = LibraryGroups.DATASTORE
+ inceptionYear = "2020"
+ description = "Jarjar the generated proto and proto-lite dependency for use by " +
+ "datastore-preferences."
+}
diff --git a/datastore/datastore-preferences/datastore-preferences-proto/src/main/AndroidManifest.xml b/datastore/datastore-preferences/datastore-preferences-proto/src/main/AndroidManifest.xml
new file mode 100644
index 0000000..384c619
--- /dev/null
+++ b/datastore/datastore-preferences/datastore-preferences-proto/src/main/AndroidManifest.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright 2020 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ -->
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="androidx.datastore.preferences.proto">
+
+</manifest>
\ No newline at end of file
diff --git a/datastore/datastore-preferences/datastore-preferences-proto/src/main/java/androidx/datastore/preferences/PreferencesMapCompat.kt b/datastore/datastore-preferences/datastore-preferences-proto/src/main/java/androidx/datastore/preferences/PreferencesMapCompat.kt
new file mode 100644
index 0000000..b7a00c6
--- /dev/null
+++ b/datastore/datastore-preferences/datastore-preferences-proto/src/main/java/androidx/datastore/preferences/PreferencesMapCompat.kt
@@ -0,0 +1,37 @@
+/*
+ * Copyright 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.datastore.preferences
+
+import androidx.datastore.CorruptionException
+import com.google.protobuf.InvalidProtocolBufferException
+import java.io.InputStream
+
+/**
+ * Read PreferenceMap proto but convert InvalidProtocolBufferExceptions to CorruptionExceptions.
+ * @hide
+ */
+class PreferencesMapCompat {
+ companion object {
+ fun readFrom(input: InputStream): PreferencesProto.PreferenceMap {
+ return try {
+ PreferencesProto.PreferenceMap.parseFrom(input)
+ } catch (ipbe: InvalidProtocolBufferException) {
+ throw CorruptionException("Unable to parse preferences proto.", ipbe)
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/datastore/datastore-preferences/src/main/proto/preferences.proto b/datastore/datastore-preferences/datastore-preferences-proto/src/main/proto/preferences.proto
similarity index 100%
rename from datastore/datastore-preferences/src/main/proto/preferences.proto
rename to datastore/datastore-preferences/datastore-preferences-proto/src/main/proto/preferences.proto
diff --git a/datastore/datastore-preferences/src/androidTest/java/androidx/datastore/preferences/PreferenceDataStoreFactoryTest.kt b/datastore/datastore-preferences/src/androidTest/java/androidx/datastore/preferences/PreferenceDataStoreFactoryTest.kt
new file mode 100644
index 0000000..8a7cccc
--- /dev/null
+++ b/datastore/datastore-preferences/src/androidTest/java/androidx/datastore/preferences/PreferenceDataStoreFactoryTest.kt
@@ -0,0 +1,155 @@
+/*
+ * Copyright 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.datastore.preferences
+
+import android.content.Context
+import androidx.datastore.DataMigration
+import androidx.datastore.handlers.ReplaceFileCorruptionHandler
+import androidx.test.core.app.ApplicationProvider
+import kotlinx.coroutines.FlowPreview
+import kotlinx.coroutines.ObsoleteCoroutinesApi
+import kotlinx.coroutines.flow.first
+import kotlinx.coroutines.test.TestCoroutineScope
+import kotlinx.coroutines.test.runBlockingTest
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.junit.rules.TemporaryFolder
+import java.io.File
+import kotlin.test.assertEquals
+
+@ObsoleteCoroutinesApi
+@kotlinx.coroutines.ExperimentalCoroutinesApi
+@FlowPreview
+class PreferenceDataStoreFactoryTest {
+ @get:Rule
+ val tmp = TemporaryFolder()
+
+ private lateinit var testFile: File
+ private lateinit var dataStoreScope: TestCoroutineScope
+ private lateinit var context: Context
+
+ val stringKey = preferencesKey<String>("key")
+ val booleanKey = preferencesKey<Boolean>("key")
+
+ @Before
+ fun setUp() {
+ testFile = tmp.newFile("test_file." + PreferencesSerializer.fileExtension)
+ dataStoreScope = TestCoroutineScope()
+ context = ApplicationProvider.getApplicationContext()
+ }
+
+ @Test
+ fun testNewInstance() = runBlockingTest {
+ val store = PreferenceDataStoreFactory.create(
+ produceFile = { testFile },
+ scope = dataStoreScope
+ )
+
+ val expectedPreferences = preferencesOf(stringKey to "value")
+
+ assertEquals(store.edit { prefs ->
+ prefs[stringKey] = "value"
+ }, expectedPreferences)
+ assertEquals(expectedPreferences, store.data.first())
+ }
+
+ @Test
+ fun testCorruptionHandlerInstalled() = runBlockingTest {
+ testFile.writeBytes(byteArrayOf(0x00, 0x00, 0x00, 0x03)) // Protos can not start with 0x00.
+
+ val valueToReplace = preferencesOf(booleanKey to true)
+
+ val store = PreferenceDataStoreFactory.create(
+ produceFile = { testFile },
+ corruptionHandler = ReplaceFileCorruptionHandler<Preferences> {
+ valueToReplace
+ },
+ scope = dataStoreScope
+ )
+ assertEquals(valueToReplace, store.data.first())
+ }
+
+ @Test
+ fun testMigrationsInstalled() = runBlockingTest {
+
+ val expectedPreferences = preferencesOf(stringKey to "value", booleanKey to true)
+
+ val migrateTo5 = object : DataMigration<Preferences> {
+ override suspend fun shouldMigrate(currentData: Preferences) = true
+
+ override suspend fun migrate(currentData: Preferences) =
+ currentData.toMutablePreferences().apply { set(stringKey, "value") }.toPreferences()
+
+ override suspend fun cleanUp() {}
+ }
+
+ val migratePlus1 = object : DataMigration<Preferences> {
+ override suspend fun shouldMigrate(currentData: Preferences) = true
+
+ override suspend fun migrate(currentData: Preferences) =
+ currentData.toMutablePreferences().apply { set(booleanKey, true) }.toPreferences()
+
+ override suspend fun cleanUp() {}
+ }
+
+ val store = PreferenceDataStoreFactory.create(
+ produceFile = { testFile },
+ migrations = listOf(migrateTo5, migratePlus1),
+ scope = dataStoreScope
+ )
+
+ assertEquals(expectedPreferences, store.data.first())
+ }
+
+ @Test
+ fun testCreateWithContextAndName() = runBlockingTest {
+ val prefs = preferencesOf(stringKey to "value")
+
+ var store = context.createDataStore(
+ name = "my_settings",
+ scope = dataStoreScope
+ )
+ store.updateData { prefs }
+
+ // Create it again and confirm it's still there
+ store = context.createDataStore("my_settings", scope = dataStoreScope)
+ assertEquals(prefs, store.data.first())
+
+ // Check that the file name is context.filesDir + name + ".preferences_pb"
+ store = PreferenceDataStoreFactory.create(produceFile = {
+ File(context.filesDir, "datastore/my_settings.preferences_pb")
+ }, scope = dataStoreScope)
+ assertEquals(prefs, store.data.first())
+ }
+
+ @Test
+ fun testCantMutateInternalState() = runBlockingTest {
+ val store =
+ PreferenceDataStoreFactory.create(produceFile = { testFile }, scope = dataStoreScope)
+
+ var mutableReference: MutablePreferences? = null
+ store.edit {
+ mutableReference = it
+ it[stringKey] = "ABCDEF"
+ }
+
+ assertEquals(store.data.first(), preferencesOf(stringKey to "ABCDEF"))
+ mutableReference!!.clear()
+ assertEquals(store.data.first(), preferencesOf(stringKey to "ABCDEF"))
+ }
+}
\ No newline at end of file
diff --git a/datastore/datastore-preferences/src/androidTest/java/androidx/datastore/preferences/SharedPreferencesToPreferencesTest.kt b/datastore/datastore-preferences/src/androidTest/java/androidx/datastore/preferences/SharedPreferencesToPreferencesTest.kt
index c92da6e..567ebc5 100644
--- a/datastore/datastore-preferences/src/androidTest/java/androidx/datastore/preferences/SharedPreferencesToPreferencesTest.kt
+++ b/datastore/datastore-preferences/src/androidTest/java/androidx/datastore/preferences/SharedPreferencesToPreferencesTest.kt
@@ -60,10 +60,10 @@
@Test
fun existingKey_isMigrated() = runBlockingTest {
- val stringKey = "string_key"
+ val stringKey = preferencesKey<String>("string_key")
val stringValue = "string value"
- assertTrue { sharedPrefs.edit().putString(stringKey, stringValue).commit() }
+ assertTrue { sharedPrefs.edit().putString(stringKey.name, stringValue).commit() }
val migration = SharedPreferencesMigration(
context = context,
@@ -74,16 +74,16 @@
val prefs = preferencesStore.data.first()
- assertEquals(stringValue, prefs.getString(stringKey, "default_string"))
- assertEquals(prefs.getAll().size, 1)
+ assertEquals(stringValue, prefs[stringKey])
+ assertEquals(prefs.asMap().size, 1)
}
@Test
fun existingKey_isRemovedFromSharedPrefs() = runBlockingTest {
- val stringKey = "string_key"
+ val stringKey = preferencesKey<String>("string_key")
val stringValue = "string value"
- assertTrue { sharedPrefs.edit().putString(stringKey, stringValue).commit() }
+ assertTrue { sharedPrefs.edit().putString(stringKey.name, stringValue).commit() }
val migration = SharedPreferencesMigration(
context = context,
@@ -95,15 +95,15 @@
// Get data so migration is run.
preferencesStore.data.first()
- assertFalse(sharedPrefs.contains(stringKey))
+ assertFalse(sharedPrefs.contains(stringKey.name))
}
@Test
fun supportsStringKey() = runBlockingTest {
- val stringKey = "string_key"
+ val stringKey = preferencesKey<String>("string_key")
val stringValue = "string_value"
- assertTrue { sharedPrefs.edit().putString(stringKey, stringValue).commit() }
+ assertTrue { sharedPrefs.edit().putString(stringKey.name, stringValue).commit() }
val migration = SharedPreferencesMigration(
context = context,
@@ -112,16 +112,16 @@
val preferencesStore = getDataStoreWithMigrations(listOf(migration))
val prefs = preferencesStore.data.first()
- assertEquals(stringValue, prefs.getString(stringKey, "default_string"))
- assertEquals(prefs.getAll().size, 1)
+ assertEquals(stringValue, prefs[stringKey])
+ assertEquals(prefs.asMap().size, 1)
}
@Test
fun supportsIntegerKey() = runBlockingTest {
- val integerKey = "integer_key"
+ val integerKey = preferencesKey<Int>("integer_key")
val integerValue = 123
- assertTrue { sharedPrefs.edit().putInt(integerKey, integerValue).commit() }
+ assertTrue { sharedPrefs.edit().putInt(integerKey.name, integerValue).commit() }
val migration = SharedPreferencesMigration(
context = context,
@@ -130,16 +130,16 @@
val preferencesStore = getDataStoreWithMigrations(listOf(migration))
val prefs = preferencesStore.data.first()
- assertEquals(integerValue, prefs.getInt(integerKey, -1))
- assertEquals(1, prefs.getAll().size)
+ assertEquals(integerValue, prefs[integerKey])
+ assertEquals(1, prefs.asMap().size)
}
@Test
fun supportsFloatKey() = runBlockingTest {
- val floatKey = "float_key"
+ val floatKey = preferencesKey<Float>("float_key")
val floatValue = 123.0f
- assertTrue { sharedPrefs.edit().putFloat(floatKey, floatValue).commit() }
+ assertTrue { sharedPrefs.edit().putFloat(floatKey.name, floatValue).commit() }
val migration = SharedPreferencesMigration(
context = context,
@@ -148,16 +148,16 @@
val preferencesStore = getDataStoreWithMigrations(listOf(migration))
val prefs = preferencesStore.data.first()
- assertEquals(floatValue, prefs.getFloat(floatKey, -1.0f))
- assertEquals(1, prefs.getAll().size)
+ assertEquals(floatValue, prefs[floatKey])
+ assertEquals(1, prefs.asMap().size)
}
@Test
fun supportsBooleanKey() = runBlockingTest {
- val booleanKey = "boolean_key"
+ val booleanKey = preferencesKey<Boolean>("boolean_key")
val booleanValue = true
- assertTrue { sharedPrefs.edit().putBoolean(booleanKey, booleanValue).commit() }
+ assertTrue { sharedPrefs.edit().putBoolean(booleanKey.name, booleanValue).commit() }
val migration = SharedPreferencesMigration(
context = context,
@@ -166,16 +166,16 @@
val preferencesStore = getDataStoreWithMigrations(listOf(migration))
val prefs = preferencesStore.data.first()
- assertEquals(booleanValue, prefs.getBoolean(booleanKey, false))
- assertEquals(1, prefs.getAll().size)
+ assertEquals(booleanValue, prefs[booleanKey])
+ assertEquals(1, prefs.asMap().size)
}
@Test
fun supportsLongKey() = runBlockingTest {
- val longKey = "long_key"
+ val longKey = preferencesKey<Long>("long_key")
val longValue = 1L shr 50
- assertTrue { sharedPrefs.edit().putLong(longKey, longValue).commit() }
+ assertTrue { sharedPrefs.edit().putLong(longKey.name, longValue).commit() }
val migration = SharedPreferencesMigration(
context = context,
@@ -184,16 +184,16 @@
val preferencesStore = getDataStoreWithMigrations(listOf(migration))
val prefs = preferencesStore.data.first()
- assertEquals(longValue, prefs.getLong(longKey, -1))
- assertEquals(1, prefs.getAll().size)
+ assertEquals(longValue, prefs[longKey])
+ assertEquals(1, prefs.asMap().size)
}
@Test
fun supportsStringSetKey() = runBlockingTest {
- val stringSetKey = "stringSet_key"
+ val stringSetKey = preferencesSetKey<String>("stringSet_key")
val stringSetValue = setOf("a", "b", "c")
- assertTrue { sharedPrefs.edit().putStringSet(stringSetKey, stringSetValue).commit() }
+ assertTrue { sharedPrefs.edit().putStringSet(stringSetKey.name, stringSetValue).commit() }
val migration = SharedPreferencesMigration(
context = context,
@@ -202,17 +202,17 @@
val preferencesStore = getDataStoreWithMigrations(listOf(migration))
val prefs = preferencesStore.data.first()
- assertEquals(stringSetValue, prefs.getStringSet(stringSetKey, setOf()))
- assertEquals(1, prefs.getAll().size)
+ assertEquals(stringSetValue, prefs[stringSetKey])
+ assertEquals(1, prefs.asMap().size)
}
@Test
fun migratedStringSetNotMutable() = runBlockingTest {
- val stringSetKey = "stringSet_key"
+ val stringSetKey = preferencesSetKey<String>("stringSet_key")
val stringSetValue = setOf("a", "b", "c")
- assertTrue { sharedPrefs.edit().putStringSet(stringSetKey, stringSetValue).commit() }
- val sharedPrefsSet = sharedPrefs.getStringSet(stringSetKey, mutableSetOf())!!
+ assertTrue { sharedPrefs.edit().putStringSet(stringSetKey.name, stringSetValue).commit() }
+ val sharedPrefsSet = sharedPrefs.getStringSet(stringSetKey.name, mutableSetOf())!!
assertEquals(stringSetValue, sharedPrefsSet)
val migration = SharedPreferencesMigration(
@@ -227,20 +227,20 @@
// Modify the sharedPrefs string set:
sharedPrefsSet.add("d")
- assertEquals(stringSetValue, prefs.getStringSet(stringSetKey, setOf<String>()))
- assertEquals(1, prefs.getAll().size)
+ assertEquals(stringSetValue, prefs[stringSetKey])
+ assertEquals(1, prefs.asMap().size)
}
@Test
fun sharedPreferencesFileDeletedIfPrefsEmpty() = runBlockingTest {
- val integerKey = "integer_key"
+ val integerKey = preferencesKey<Int>("integer_key")
- assertTrue { sharedPrefs.edit().putInt(integerKey, 123).commit() }
+ assertTrue { sharedPrefs.edit().putInt(integerKey.name, 123).commit() }
val migration = SharedPreferencesMigration(
context = context,
sharedPreferencesName = sharedPrefsName,
- keysToMigrate = setOf(integerKey),
+ keysToMigrate = setOf(integerKey.name),
deleteEmptyPreferences = true
)
@@ -252,14 +252,14 @@
@Test
fun sharedPreferencesFileNotDeletedIfDisabled() = runBlockingTest {
- val integerKey = "integer_key"
+ val integerKey = preferencesKey<Int>("integer_key")
- assertTrue { sharedPrefs.edit().putInt(integerKey, 123).commit() }
+ assertTrue { sharedPrefs.edit().putInt(integerKey.name, 123).commit() }
val migration = SharedPreferencesMigration(
context = context,
sharedPreferencesName = sharedPrefsName,
- keysToMigrate = setOf(integerKey),
+ keysToMigrate = setOf(integerKey.name),
deleteEmptyPreferences = false
)
@@ -273,17 +273,17 @@
@Test
fun sharedPreferencesFileNotDeletedIfPrefsNotEmpty() = runBlockingTest {
- val integerKey1 = "integer_key1"
- val integerKey2 = "integer_key2"
+ val integerKey1 = preferencesKey<Int>("integer_key1")
+ val integerKey2 = preferencesKey<Int>("integer_key2")
assertTrue {
- sharedPrefs.edit().putInt(integerKey1, 123).putInt(integerKey2, 123).commit()
+ sharedPrefs.edit().putInt(integerKey1.name, 123).putInt(integerKey2.name, 123).commit()
}
val migration = SharedPreferencesMigration(
context = context,
sharedPreferencesName = sharedPrefsName,
- keysToMigrate = setOf(integerKey1),
+ keysToMigrate = setOf(integerKey1.name),
deleteEmptyPreferences = true
)
@@ -295,19 +295,19 @@
@Test
fun sharedPreferencesBackupFileDeleted() = runBlockingTest {
- val integerKey = "integer_key"
+ val integerKey = preferencesKey<Int>("integer_key")
// Write to shared preferences then create the backup file
val sharedPrefsFile = getSharedPrefsFile(context, sharedPrefsName)
val sharedPrefsBackupFile = getSharedPrefsBackup(sharedPrefsFile)
- assertTrue { sharedPrefs.edit().putInt(integerKey, 123).commit() }
+ assertTrue { sharedPrefs.edit().putInt(integerKey.name, 123).commit() }
assertTrue { sharedPrefsFile.exists() }
assertTrue { sharedPrefsFile.renameTo(sharedPrefsBackupFile) }
val migration = SharedPreferencesMigration(
context = context,
sharedPreferencesName = sharedPrefsName,
- keysToMigrate = setOf(integerKey),
+ keysToMigrate = setOf(integerKey.name),
deleteEmptyPreferences = true
)
@@ -319,8 +319,8 @@
@Test
fun canSpecifyMultipleKeys() = runBlockingTest {
- val stringKey = "string_key"
- val integerKey = "integer_key"
+ val stringKey = preferencesKey<String>("string_key")
+ val integerKey = preferencesKey<Int>("integer_key")
val keyNotMigrated = "dont_migrate_this_key"
val stringValue = "string_value"
@@ -329,8 +329,8 @@
assertTrue {
sharedPrefs.edit()
- .putString(stringKey, stringValue)
- .putInt(integerKey, intValue)
+ .putString(stringKey.name, stringValue)
+ .putInt(integerKey.name, intValue)
.putString(keyNotMigrated, notMigratedString)
.commit()
}
@@ -338,62 +338,62 @@
val migration = SharedPreferencesMigration(
context = context,
sharedPreferencesName = sharedPrefsName,
- keysToMigrate = setOf(stringKey, integerKey)
+ keysToMigrate = setOf(stringKey.name, integerKey.name)
)
val preferencesStore = getDataStoreWithMigrations(listOf(migration))
val prefs = preferencesStore.data.first()
- assertEquals(stringValue, prefs.getString(stringKey, "default_string"))
- assertEquals(intValue, prefs.getInt(integerKey, -1))
+ assertEquals(stringValue, prefs[stringKey])
+ assertEquals(intValue, prefs[integerKey])
- assertEquals(2, prefs.getAll().size)
+ assertEquals(2, prefs.asMap().size)
assertEquals(notMigratedString, sharedPrefs.getString(keyNotMigrated, ""))
}
@Test
fun missingSpecifiedKeyIsNotMigrated() = runBlockingTest {
- val missingKey = "missing_key"
+ val missingKey = preferencesKey<Int>("missing_key")
val migration = SharedPreferencesMigration(
context = context,
sharedPreferencesName = sharedPrefsName,
- keysToMigrate = setOf(missingKey)
+ keysToMigrate = setOf(missingKey.name)
)
val preferencesStore = getDataStoreWithMigrations(listOf(migration))
val prefs = preferencesStore.data.first()
assertFalse(prefs.contains(missingKey))
- assertFalse(prefs.getAll().containsKey(missingKey))
+ assertEquals(0, prefs.asMap().size)
}
@Test
fun runsIfAnySpecifiedKeyExists() = runBlockingTest {
- val integerKey = "integer_key"
- val missingKey = "missing_key"
+ val integerKey = preferencesKey<Int>("integer_key")
+ val missingKey = preferencesKey<Int>("missing_key")
val integerValue = 123
- assertTrue { sharedPrefs.edit().putInt(integerKey, integerValue).commit() }
+ assertTrue { sharedPrefs.edit().putInt(integerKey.name, integerValue).commit() }
val migration = SharedPreferencesMigration(
context = context,
sharedPreferencesName = sharedPrefsName,
- keysToMigrate = setOf(integerKey, missingKey)
+ keysToMigrate = setOf(integerKey.name, missingKey.name)
)
val preferencesStore = getDataStoreWithMigrations(listOf(migration))
val prefs = preferencesStore.data.first()
- assertEquals(integerValue, prefs.getInt(integerKey, -1))
- assertEquals(1, prefs.getAll().size)
+ assertEquals(integerValue, prefs[integerKey])
+ assertEquals(1, prefs.asMap().size)
}
private fun getDataStoreWithMigrations(
migrations: List<DataMigration<Preferences>>
): DataStore<Preferences> {
- return PreferenceDataStoreFactory().create(
+ return PreferenceDataStoreFactory.create(
produceFile = { datastoreFile },
migrations = migrations,
scope = TestCoroutineScope()
diff --git a/datastore/datastore-preferences/src/main/java/androidx/datastore/preferences/PreferenceDataStoreFactory.kt b/datastore/datastore-preferences/src/main/java/androidx/datastore/preferences/PreferenceDataStoreFactory.kt
index 7de4135..02ebae6 100644
--- a/datastore/datastore-preferences/src/main/java/androidx/datastore/preferences/PreferenceDataStoreFactory.kt
+++ b/datastore/datastore-preferences/src/main/java/androidx/datastore/preferences/PreferenceDataStoreFactory.kt
@@ -16,6 +16,7 @@
package androidx.datastore.preferences
+import android.content.Context
import androidx.datastore.CorruptionException
import androidx.datastore.DataMigration
import androidx.datastore.DataStore
@@ -29,33 +30,30 @@
/**
* Public factory for creating PreferenceDataStore instances.
*/
-class PreferenceDataStoreFactory {
- private val dataStoreFactory = DataStoreFactory()
-
+object PreferenceDataStoreFactory {
/**
* Create an instance of SingleProcessDataStore. The user is responsible for ensuring that
* there is never more than one instance of SingleProcessDataStore acting on a file at a time.
*
- * @param produceFile Function which returns the file that the new DataStore will act on. The function
- * must return the same path every time. No two instances of PreferenceDataStore
+ * @param produceFile Function which returns the file that the new DataStore will act on.
+ * The function must return the same path every time. No two instances of PreferenceDataStore
* should act on the same file at the same time. The file must have the extension
* preferences_pb.
- * @param corruptionHandler The corruptionHandler is invoked if DataStore encounters a [CorruptionException] when
- * attempting to read data. CorruptionExceptions are thrown by serializers when data can
- * not be de-serialized.
- * @param migrations are run before any access to data can occur. Each
- * producer and migration may be run more than once whether or not it already succeeded
- * (potentially because another migration failed or a write to disk failed.)
+ * @param corruptionHandler The corruptionHandler is invoked if DataStore encounters a
+ * [CorruptionException] when attempting to read data. CorruptionExceptions are thrown by
+ * serializers when data cannot be de-serialized.
+ * @param migrations are run before any access to data can occur. Each producer and migration
+ * may be run more than once whether or not it already succeeded (potentially because another
+ * migration failed or a write to disk failed.)
* @param scope The scope in which IO operations and transform functions will execute.
*/
- @JvmOverloads // Generate methods for default params for java users.
fun create(
produceFile: () -> File,
corruptionHandler: ReplaceFileCorruptionHandler<Preferences>? = null,
migrations: List<DataMigration<Preferences>> = listOf(),
scope: CoroutineScope = CoroutineScope(Dispatchers.IO + SupervisorJob())
- ): DataStore<Preferences> =
- dataStoreFactory.create(
+ ): DataStore<Preferences> {
+ val delegate = DataStoreFactory.create(
produceFile = {
val file = produceFile()
check(file.extension == PreferencesSerializer.fileExtension) {
@@ -69,4 +67,46 @@
migrations = migrations,
scope = scope
)
-}
\ No newline at end of file
+ return PreferenceDataStore(delegate)
+ }
+}
+
+/**
+ * Create an instance of SingleProcessDataStore. The user is responsible for ensuring that
+ * there is never more than one instance of SingleProcessDataStore acting on a file at a time.
+ *
+ * @param name The name of the preferences. The preferences will be stored in a file obtained
+ * by calling: File(context.filesDir, "datastore/" + name + ".preferences_pb")
+ * @param corruptionHandler The corruptionHandler is invoked if DataStore encounters a
+ * [CorruptionException] when attempting to read data. CorruptionExceptions are thrown by
+ * serializers when data can not be de-serialized.
+ * @param migrations are run before any access to data can occur. Each producer and migration
+ * may be run more than once whether or not it already succeeded (potentially because another
+ * migration failed or a write to disk failed.)
+ * @param scope The scope in which IO operations and transform functions will execute.
+ */
+fun Context.createDataStore(
+ name: String,
+ corruptionHandler: ReplaceFileCorruptionHandler<Preferences>? = null,
+ migrations: List<DataMigration<Preferences>> = listOf(),
+ scope: CoroutineScope = CoroutineScope(Dispatchers.IO + SupervisorJob())
+): DataStore<Preferences> =
+ PreferenceDataStoreFactory.create(
+ produceFile = {
+ File(this.filesDir, "datastore/$name.preferences_pb")
+ },
+ corruptionHandler = corruptionHandler,
+ migrations = migrations,
+ scope = scope
+ )
+
+internal class PreferenceDataStore(private val delegate: DataStore<Preferences>) :
+ DataStore<Preferences> by delegate {
+ override suspend fun updateData(transform: suspend (t: Preferences) -> Preferences):
+ Preferences {
+ return delegate.updateData {
+ // Make a defensive copy
+ transform(it).toPreferences()
+ }
+ }
+}
diff --git a/datastore/datastore-preferences/src/main/java/androidx/datastore/preferences/Preferences.kt b/datastore/datastore-preferences/src/main/java/androidx/datastore/preferences/Preferences.kt
index 9ec3678..b39fa12 100644
--- a/datastore/datastore-preferences/src/main/java/androidx/datastore/preferences/Preferences.kt
+++ b/datastore/datastore-preferences/src/main/java/androidx/datastore/preferences/Preferences.kt
@@ -15,264 +15,355 @@
*/
package androidx.datastore.preferences
+import androidx.datastore.DataStore
+import java.lang.IllegalArgumentException
+import java.util.Collections
+
/**
- * Preferences provides a schemaless key-value format for use with DataStore. It closely
- * resembles the SharedPreferences interface, with a few differences to allow compatibility with
- * DataStore. The differences include:
- * 1. There is no edit() method. Instead use the toBuilder() to create a new PreferencesBuilder
- * and pass the result to DataStore.updateData().
- * 2. There is no (un)registerOnSharedPreferenceChangeListener() methods. Instead, use DataStore
- * .data.
+ * Get a preference Key of type T. Type T must be one of: Int, Long, Boolean, Float, String.
+ * Use the [preferencesSetKey] function to create a preference key for Set<String>. No
+ * other types are supported.
+ *
+ * You should not have multiple keys with the same name (for use with the same Preferences).
+ * Using overlapping keys with different types will result in ClassCastExceptions.
+ *
+ * @return the Preference.Key object for your preference
+ * @throws IllegalArgumentException if an unsupported type is used
*/
-class Preferences internal constructor(
- private val preferences: Map<String, Any> = mapOf()
-) {
-
- /* Checks whether the Preferences contains a preference. */
- operator fun contains(key: String): Boolean {
- return preferences.containsKey(key)
+inline fun <reified T : Any> preferencesKey(name: String): Preferences.Key<T> {
+ return when (T::class) {
+ Int::class -> {
+ Preferences.Key<T>(name)
+ }
+ String::class -> {
+ Preferences.Key<T>(name)
+ }
+ Boolean::class -> {
+ Preferences.Key<T>(name)
+ }
+ Float::class -> {
+ Preferences.Key<T>(name)
+ }
+ Long::class -> {
+ Preferences.Key<T>(name)
+ }
+ Set::class -> {
+ throw IllegalArgumentException("Use `preferencesSetKey` to create keys for Sets.")
+ }
+ else -> {
+ throw IllegalArgumentException("Type not supported: ${T::class.java}")
+ }
}
+}
- /**
- * Retrieve a boolean value from Preferences.
- *
- * @param key The name of the preference to retrieve.
- * @param defaultValue Value to return if this preference does not exist.
- *
- * @return Returns the preference if it exists, otherwise returns defaultValue
- *
- * @throws ClassCastException if there is a preference for this key that is not a boolean.
- */
- fun getBoolean(key: String, defaultValue: Boolean): Boolean {
- return getKeyOrDefault(key, defaultValue)
+/**
+ * Get a preference key for Set<T>. The only type currently supported for T is String.
+ *
+ * Note: sets returned by DataStore are unmodifiable.
+ * @throws IllegalArgumentException if an unsupported type is used
+ * @return the Preference.Key object for use with Preferences
+ */
+inline fun <reified T : Any> preferencesSetKey(name: String): Preferences.Key<Set<T>> {
+ if (T::class == String::class) {
+ return Preferences.Key(name)
+ } else {
+ throw IllegalArgumentException("Only String sets are currently supported.")
}
+}
- /**
- * Retrieve a float value from Preferences.
- *
- * @param key The name of the preference to retrieve.
- * @param defaultValue Value to return if this preference does not exist.
- *
- * @return Returns the preference if it exists, otherwise returns defaultValue
- *
- * @throws ClassCastException if there is a preference for this key that is not a float.
- */
- fun getFloat(key: String, defaultValue: Float): Float {
- return getKeyOrDefault(key, defaultValue)
- }
+/**
+ * Get a new empty Preferences.
+ *
+ * @return a new Preferences instance with no preferences set
+ */
+fun emptyPreferences(): Preferences = MutablePreferences()
- /**
- * Retrieve a int value from Preferences.
- *
- * @param key The name of the preference to retrieve.
- * @param defaultValue Value to return if this preference does not exist.
- *
- * @return Returns the preference if it exists, otherwise returns defaultValue
- *
- * @throws ClassCastException if there is a preference for this key that is not a int.
- */
- fun getInt(key: String, defaultValue: Int): Int {
- return getKeyOrDefault(key, defaultValue)
- }
+/**
+ * Construct a Preferences object with a list of Preferences.Pair<T>. Comparable to mapOf().
+ *
+ * Example usage:
+ *
+ * val counterKey = preferencesKey<Int>("counter")
+ * val preferences = preferencesOf(counterKey to 100)
+ *
+ * @param pairs
+ */
+fun preferencesOf(vararg pairs: Preferences.Pair<*>): Preferences = mutablePreferencesOf(*pairs)
- /**
- * Retrieve a long value from Preferences.
- *
- * @param key The name of the preference to retrieve.
- * @param defaultValue Value to return if this preference does not exist.
- *
- * @return Returns the preference if it exists, otherwise returns defaultValue
- *
- * @throws ClassCastException if there is a preference for this key that is not a long.
- */
- fun getLong(key: String, defaultValue: Long): Long {
- return getKeyOrDefault(key, defaultValue)
- }
+/**
+ * Construct a MutablePreferences object with a list of Preferences.Pair<T>. Comparable to mapOf().
+ *
+ * Example usage:
+ *
+ * val counterKey = preferencesKey<Int>("counter")
+ * val preferences = preferencesOf(counterKey to 100)
+ *
+ * @param pairs
+ */
+fun mutablePreferencesOf(vararg pairs: Preferences.Pair<*>): MutablePreferences =
+ MutablePreferences().apply { putAll(*pairs) }
- /**
- * Retrieve a String value from Preferences.
- *
- * @param key The name of the preference to retrieve.
- * @param defaultValue Value to return if this preference does not exist.
- *
- * @return Returns the preference if it exists, otherwise returns defaultValue
- *
- * @throws ClassCastException if there is a preference for this key that is not a String.
- */
- fun getString(key: String, defaultValue: String): String {
- return getKeyOrDefault(key, defaultValue)
- }
+/**
+ * Infix function to create a Preferences.Pair.
+ * This is used to support [preferencesOf] and [MutablePreferences.putAll]
+ * @param value is the value this preferences key should point to.
+ */
+infix fun <T> Preferences.Key<T>.to(value: T): Preferences.Pair<T> = Preferences.Pair(this, value)
+/**
+ * Preferences and MutablePreferences are a lot like a generic Map and MutableMap keyed by the
+ * Preferences.Key class. These are intended for use with DataStore. Construct a
+ * DataStore<Preferences> instance using [PreferenceDataStoreFactory.create].
+ */
+abstract class Preferences internal constructor() {
/**
- * Retrieve a set of Strings from Preferences.
+ * Key for values stored in Preferences. Type T is the type of the value associated with the
+ * Key.
*
- * @param key The name of the preference to retrieve.
- * @param defaultValue Value to return if this preference does not exist.
+ * T must be one of the following: Boolean, Int, Long, Float, String, Set<String>.
*
- * @return Returns the preference if it exists, otherwise returns defaultValue
- *
- * @throws ClassCastException if there is a preference for this key that is not a Set of
- * Strings.
+ * Construct Keys for your data type using: [preferencesKey], [preferencesSetKey].
*/
- fun getStringSet(key: String, defaultValue: Set<String>): Set<String> {
- return getKeyOrDefault(key, defaultValue).toSet()
- }
-
- /**
- * Retrieve a map of all values from the preferences.
- *
- * @return Returns a map containing representing all the preferences in Preferences.
- */
- fun getAll(): Map<String, Any> {
- return preferences.mapValues {
- val value = it.value
- if (value is Set<*>) {
- value.toSet()
+ class Key<T>
+ @PublishedApi // necessary to use this in the public inline function preferencesKey().
+ internal constructor(val name: String) {
+ override fun equals(other: Any?) =
+ if (other is Key<*>) {
+ name == other.name
} else {
- value
+ false
}
+
+ override fun hashCode(): Int {
+ return name.hashCode()
}
}
- // TODO(b/151635324): add getByteArray()... ByteArray, Byte[], List<Byte>?
+ /**
+ * Key Value pairs for Preferences. Type T is the type of the value.
+ *
+ * Construct these using the infix function [to].
+ */
+ class Pair<T> internal constructor(internal val key: Key<T>, internal val value: T)
+ /* Checks whether Preferences contains a key. */
+ abstract operator fun <T> contains(key: Key<T>): Boolean
+
+ /**
+ * Get a preference with a key. If the key is not set, returns null.
+ *
+ * If T is Set<String>, this returns an unmodifiable set which will throw a runtime exception
+ * when mutated. Do not try to mutate the returned set.
+ *
+ * Use [MutablePreferences.set] to change the value of a preference (inside a
+ * [DataStore<Preferences>.edit] block).
+ *
+ * @param T the type of the preference
+ * @param key the key for the preference
+ * @throws ClassCastException if there is something stored with the same name as [key] but
+ * it cannot be cast to T
+ */
+ abstract operator fun <T> get(key: Key<T>): T?
+
+ /**
+ * Retrieve a map of all key preference pairs. The returned map is unmodifiable, and attempts
+ * to mutate it will throw runtime exceptions.
+ *
+ * @return a map containing all the preferences in this Preferences
+ */
+ abstract fun asMap(): Map<Key<*>, Any>
+}
+
+/**
+ * Mutable version of [Preferences]. Allows for creating Preferences with different key-value pairs.
+ */
+class MutablePreferences internal constructor(
+ internal val preferencesMap: MutableMap<Key<*>, Any> = mutableMapOf()
+) : Preferences() {
+ override operator fun <T> contains(key: Key<T>): Boolean {
+ return preferencesMap.containsKey(key)
+ }
+
+ override operator fun <T> get(key: Preferences.Key<T>): T? {
+ @Suppress("UNCHECKED_CAST")
+ return preferencesMap[key] as T?
+ }
+
+ override fun asMap(): Map<Key<*>, Any> {
+ return Collections.unmodifiableMap(preferencesMap)
+ }
+
+ // Mutating methods below:
+
+ /**
+ * Set a key value pair in MutablePreferences.
+ *
+ * Example usage:
+ * val COUNTER_KEY = preferencesKey<Int>("counter")
+ *
+ * // Once edit completes successfully, preferenceStore will contain the incremented counter.
+ * preferenceStore.edit { prefs: MutablePreferences ->
+ * prefs\[COUNTER_KEY\] = prefs\[COUNTER_KEY\] :? 0 + 1
+ * }
+ *
+ * @param key the preference to set
+ * @param key the value to set the preference to
+ */
+ operator fun <T> set(key: Key<T>, value: T) {
+ setUnchecked(key, value)
+ }
+
+ /**
+ * Private setter function. The type of key and value *must* be the same.
+ */
+ internal fun setUnchecked(key: Key<*>, value: Any?) {
+ when (value) {
+ null -> remove(key)
+ // Copy set so changes to input don't change Preferences. Wrap in unmodifiableSet so
+ // returned instances can't be changed.
+ is Set<*> -> preferencesMap[key] = Collections.unmodifiableSet(value.toSet())
+ else -> preferencesMap[key] = value
+ }
+ }
+
+ // Equals and hash code for use by DataStore
override fun equals(other: Any?): Boolean {
- if (other is Preferences) {
- return this.preferences == other.preferences
+ if (other is MutablePreferences) {
+ return preferencesMap == other.preferencesMap
}
return false
}
override fun hashCode(): Int {
- return preferences.hashCode()
+ return preferencesMap.hashCode()
}
+}
- /**
- * Gets a builder which contains all the preferences in this Preferences. This can be used
- * to change preferences without building a new Preferences object from scratch.
- *
- * @return Returns a PreferencesBuilder with all the preferences from this Preferences.
- */
- fun toBuilder(): Builder {
- return Builder(
- preferences.toMutableMap()
- )
+/**
+ * Gets a mutable copy of Preferences which contains all the preferences in this Preferences.
+ * This can be used to update your preferences without building a new Preferences object from
+ * scratch in [DataStore.updateData].
+ *
+ * This is similar to [Map.toMutableMap].
+ *
+ * @return a MutablePreferences with all the preferences from this Preferences
+ */
+fun Preferences.toMutablePreferences(): MutablePreferences {
+ return MutablePreferences(asMap().toMutableMap())
+}
+
+/**
+ * Gets a read-only copy of Preferences which contains all the preferences in this Preferences.
+ *
+ * This is similar to [Map.toMap].
+ *
+ * @return a copy of this Preferences
+ */
+fun Preferences.toPreferences(): Preferences {
+ return MutablePreferences(asMap().toMutableMap())
+}
+
+/**
+ * Appends or replaces all pairs from [prefs] to this MutablePreferences. Keys in [prefs]
+ * will overwrite keys in this Preferences.
+ *
+ * Example usage:
+ * mutablePrefs += preferencesOf(COUNTER_KEY to 100, NAME to "abcdef")
+ *
+ * @param prefs Preferences to append to this MutablePreferences
+ */
+operator fun MutablePreferences.plusAssign(prefs: Preferences) {
+ preferencesMap += prefs.asMap()
+}
+
+/**
+ * Appends or replaces all [pair] to this MutablePreferences.
+ *
+ * Example usage:
+ * mutablePrefs += COUNTER_KEY to 100
+ *
+ * @param pair the Preference.Pair to add to this MutablePreferences
+ */
+operator fun MutablePreferences.plusAssign(pair: Preferences.Pair<*>) {
+ putAll(pair)
+}
+
+/**
+ * Removes the preference with the given key from this MutablePreferences. If this
+ * Preferences does not contain the key, this is a no-op.
+ *
+ * Example usage:
+ * mutablePrefs -= COUNTER_KEY
+ *
+ * @param key the key to remove from this MutablePreferences
+ */
+operator fun MutablePreferences.minusAssign(key: Preferences.Key<*>) {
+ remove(key)
+}
+
+/**
+ * Appends or replaces all [pairs] to this MutablePreferences.
+ *
+ * @param pairs the pairs to append to this MutablePreferences
+ */
+fun MutablePreferences.putAll(vararg pairs: Preferences.Pair<*>) {
+ pairs.forEach {
+ setUnchecked(it.key, it.value)
}
+}
- companion object {
- /**
- * Get a new empty Preferences.
- *
- * @return Returns a new Preferences instance with no preferences set.
- */
- @JvmStatic
- fun empty(): Preferences {
- return Preferences()
- }
- }
+/**
+ * Remove a preferences from this MutablePreferences.
+ *
+ * @param key the key to remove this MutablePreferences
+ * @return the original value of this preference key.
+ */
+@Suppress("UNCHECKED_CAST")
+fun <T> MutablePreferences.remove(key: Preferences.Key<T>): T {
+ return preferencesMap.remove(key) as T
+}
- /**
- * The builder used for constructing Preferences. PreferencesBuilder resembles
- * SharedPreferences.Editor, with some key differences:
- * 1. It follows the builder pattern, so it cannot modify the state of any existing Preferences.
- * 2. There is no apply or commit method. Instead, pass the result of build() to
- * DataStore.updateData()
- */
- class Builder internal constructor(
- private val preferencesMap: MutableMap<String, Any> = mutableMapOf()
- ) {
- constructor() : this(mutableMapOf()) {}
+/* Removes all preferences from this MutablePreferences. */
+fun MutablePreferences.clear() = preferencesMap.clear()
- /**
- * Set a boolean value in the PreferencesBuilder.
- *
- * @param key The name of the preference to set.
- * @param newValue The new value of the preference.
- *
- * @return Returns this instance of PreferencesBuilder.
- */
- fun setBoolean(key: String, newValue: Boolean) = apply {
- preferencesMap[key] = newValue
- }
+/**
+ * Edit the value in DataStore transactionally in an atomic read-modify-write operation. All
+ * operations are serialized.
+ *
+ * The coroutine completes when the data has been persisted durably to disk (after which
+ * [DataStore.data] will reflect the update). If the transform or write to disk fails, the
+ * transaction is aborted and an exception is thrown.
+ *
+ * Note: values that are changed in [transform] are NOT updated in DataStore until after the
+ * transform completes. Do not assume that the data has been successfully persisted until after
+ * edit returns successfully.
+ *
+ * Note: do NOT store a reference to the MutablePreferences provided to transform. Mutating this
+ * after [transform] returns will NOT change the data in DataStore. Future versions of this may
+ * throw exceptions if the MutablePreferences object is mutated outside of [transform].
+ *
+ * See [DataStore.updateData].
+ *
+ * Example usage:
+ * val COUNTER_KEY = preferencesKey<Int>("my_counter")
+ *
+ * dataStore.edit { prefs ->
+ * prefs\[COUNTER_KEY\] = prefs\[COUNTER_KEY\] :? 0 + 1
+ * }
+ *
+ * @param transform block which accepts MutablePreferences that contains all the preferences
+ * currently in DataStore. Changes to this MutablePreferences object will be persisted once
+ * transform completes.
+ * @throws IOException when an exception is encountered when writing data to disk
+ * @throws Exception when thrown by the transform block
+ */
- /**
- * Set a float value in the PreferencesBuilder.
- *
- * @param key The name of the preference to set.
- * @param newValue The new value of the preference.
- *
- * @return Returns this instance of PreferencesBuilder.
- */
- fun setFloat(key: String, newValue: Float) = apply {
- preferencesMap[key] = newValue
- }
-
- /**
- * Set a int value in the PreferencesBuilder.
- *
- * @param key The name of the preference to set.
- * @param newValue The new value of the preference.
- *
- * @return Returns this instance of PreferencesBuilder.
- */
- fun setInt(key: String, newValue: Int) = apply {
- preferencesMap[key] = newValue
- }
-
- /**
- * Set a long value in the PreferencesBuilder.
- *
- * @param key The name of the preference to set.
- * @param newValue The new value of the preference.
- *
- * @return Returns this instance of PreferencesBuilder.
- */
- fun setLong(key: String, newValue: Long) = apply {
- preferencesMap[key] = newValue
- }
-
- /**
- * Set a String value in the PreferencesBuilder.
- *
- * @param key The name of the preference to set.
- * @param newValue The new value of the preference.
- *
- * @return Returns this instance of PreferencesBuilder.
- */
- fun setString(key: String, newValue: String) = apply {
- preferencesMap[key] = newValue
- }
-
- /**
- * Set a String Set in the PreferencesBuilder.
- *
- * @param key The name of the preference to set.
- * @param newValue The new value of the preference.
- *
- * @return Returns this instance of PreferencesBuilder.
- */
- fun setStringSet(key: String, newValue: Set<String>) = apply {
- preferencesMap[key] = newValue.toSet()
- }
-
- /* Remove a preferences from the PreferencesBuilder. */
- fun remove(key: String) = apply {
- preferencesMap.remove(key)
- }
-
- /* Removes all preferences from the PreferencesBuilder. */
- fun clear() = apply {
- preferencesMap.clear()
- }
-
- // TODO(b/151635324): setByteArray(...)
-
- fun build(): Preferences {
- return Preferences(preferencesMap.toMap())
- }
- }
-
- private inline fun <reified T> getKeyOrDefault(key: String, defaultValue: T): T {
- return preferences.getOrElse(key, { defaultValue }) as T
+suspend fun DataStore<Preferences>.edit(
+ transform: suspend (MutablePreferences) -> Unit
+): Preferences {
+ return this.updateData {
+ // It's safe to return MutablePreferences since we make a defensive copy in
+ // PreferencesDataStore.updateData()
+ it.toMutablePreferences().apply { transform(this) }
}
}
diff --git a/datastore/datastore-preferences/src/main/java/androidx/datastore/preferences/PreferencesSerializer.kt b/datastore/datastore-preferences/src/main/java/androidx/datastore/preferences/PreferencesSerializer.kt
index 44949c2..179066c 100644
--- a/datastore/datastore-preferences/src/main/java/androidx/datastore/preferences/PreferencesSerializer.kt
+++ b/datastore/datastore-preferences/src/main/java/androidx/datastore/preferences/PreferencesSerializer.kt
@@ -21,7 +21,6 @@
import androidx.datastore.preferences.PreferencesProto.Value
import androidx.datastore.preferences.PreferencesProto.StringSet
import androidx.datastore.Serializer
-import com.google.protobuf.InvalidProtocolBufferException
import java.io.IOException
import java.io.InputStream
import java.io.OutputStream
@@ -37,29 +36,24 @@
@Throws(IOException::class, CorruptionException::class)
override fun readFrom(input: InputStream): Preferences {
- val preferencesProto = try {
- PreferenceMap.parseFrom(input)
- } catch (invalidProtocolBufferException: InvalidProtocolBufferException) {
- throw CorruptionException(
- "Unable to parse preferences proto.",
- invalidProtocolBufferException
- )
+ val preferencesProto = PreferencesMapCompat.readFrom(input)
+
+ val mutablePreferences = mutablePreferencesOf()
+
+ preferencesProto.preferencesMap.forEach { (name, value) ->
+ addProtoEntryToPreferences(name, value, mutablePreferences)
}
- val preferencesMap = preferencesProto.preferencesMap.mapValues {
- convertProtoToObject(it.value)
- }
-
- return Preferences(preferencesMap)
+ return mutablePreferences.toPreferences()
}
@Throws(IOException::class, CorruptionException::class)
override fun writeTo(t: Preferences, output: OutputStream) {
- val preferences = t.getAll()
+ val preferences = t.asMap()
val protoBuilder = PreferenceMap.newBuilder()
for ((key, value) in preferences) {
- protoBuilder.putPreferences(key, getValueProto(value))
+ protoBuilder.putPreferences(key.name, getValueProto(value))
}
protoBuilder.build().writeTo(output)
@@ -83,14 +77,19 @@
}
}
- private fun convertProtoToObject(value: Value): Any {
+ private fun addProtoEntryToPreferences(
+ name: String,
+ value: Value,
+ mutablePreferences: MutablePreferences
+ ) {
return when (value.valueCase) {
- Value.ValueCase.BOOLEAN -> value.boolean
- Value.ValueCase.FLOAT -> value.float
- Value.ValueCase.INTEGER -> value.integer
- Value.ValueCase.LONG -> value.long
- Value.ValueCase.STRING -> value.string
- Value.ValueCase.STRING_SET -> value.stringSet.stringsList.toSet()
+ Value.ValueCase.BOOLEAN -> mutablePreferences[preferencesKey(name)] = value.boolean
+ Value.ValueCase.FLOAT -> mutablePreferences[preferencesKey(name)] = value.float
+ Value.ValueCase.INTEGER -> mutablePreferences[preferencesKey(name)] = value.integer
+ Value.ValueCase.LONG -> mutablePreferences[preferencesKey(name)] = value.long
+ Value.ValueCase.STRING -> mutablePreferences[preferencesKey(name)] = value.string
+ Value.ValueCase.STRING_SET -> mutablePreferences[preferencesSetKey<String>(name)] =
+ value.stringSet.stringsList.toSet()
Value.ValueCase.VALUE_NOT_SET ->
throw CorruptionException("Value not set.")
null -> throw CorruptionException("Value case is null.")
diff --git a/datastore/datastore-preferences/src/main/java/androidx/datastore/preferences/SharedPreferencesMigration.kt b/datastore/datastore-preferences/src/main/java/androidx/datastore/preferences/SharedPreferencesMigration.kt
index 0e2a46c..d384899 100644
--- a/datastore/datastore-preferences/src/main/java/androidx/datastore/preferences/SharedPreferencesMigration.kt
+++ b/datastore/datastore-preferences/src/main/java/androidx/datastore/preferences/SharedPreferencesMigration.kt
@@ -54,28 +54,33 @@
shouldRunMigration = { prefs ->
// If any key hasn't been migrated to currentData, we can't skip the migration. If
// the key set is not specified, we can't skip the migration.
- keysToMigrate?.any { it !in prefs } ?: true
+ val allKeys = prefs.asMap().keys.map { it.name }
+ keysToMigrate?.any { it !in allKeys } ?: true
},
migrate = { sharedPrefs: SharedPreferencesView, currentData: Preferences ->
- // prefs.getAll is already filtered to our key set.
- val preferencesToMigrate =
- sharedPrefs.getAll().filter { (key, _) -> key !in currentData }
+ // prefs.getAll is already filtered to our key set, but we don't want to overwrite
+ // already existing keys.
+ val currentKeys = currentData.asMap().keys.map { it.name }
- val preferencesBuilder = currentData.toBuilder()
- for ((key, value) in preferencesToMigrate) {
+ val filteredSharedPreferences =
+ sharedPrefs.getAll().filter { (key, _) -> key !in currentKeys }
+
+ val mutablePreferences = currentData.toMutablePreferences()
+ for ((key, value) in filteredSharedPreferences) {
when (value) {
- is Boolean -> preferencesBuilder.setBoolean(key, value)
- is Float -> preferencesBuilder.setFloat(key, value)
- is Int -> preferencesBuilder.setInt(key, value)
- is Long -> preferencesBuilder.setLong(key, value)
- is String -> preferencesBuilder.setString(key, value)
- is Set<*> ->
+ is Boolean -> mutablePreferences[preferencesKey(key)] = value
+ is Float -> mutablePreferences[preferencesKey(key)] = value
+ is Int -> mutablePreferences[preferencesKey(key)] = value
+ is Long -> mutablePreferences[preferencesKey(key)] = value
+ is String -> mutablePreferences[preferencesKey(key)] = value
+ is Set<*> -> {
@Suppress("UNCHECKED_CAST")
- preferencesBuilder.setStringSet(key, value.toSet() as Set<String>)
+ mutablePreferences[preferencesSetKey<String>(key)] = value as Set<String>
+ }
}
}
- preferencesBuilder.build()
+ mutablePreferences.toPreferences()
})
}
diff --git a/datastore/datastore-preferences/src/test/java/androidx/datastore/preferences/PreferenceDataStoreFactoryTest.kt b/datastore/datastore-preferences/src/test/java/androidx/datastore/preferences/PreferenceDataStoreFactoryTest.kt
deleted file mode 100644
index 7ae14dc..0000000
--- a/datastore/datastore-preferences/src/test/java/androidx/datastore/preferences/PreferenceDataStoreFactoryTest.kt
+++ /dev/null
@@ -1,120 +0,0 @@
-/*
- * Copyright 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package androidx.datastore.preferences
-
-import androidx.datastore.DataMigration
-import androidx.datastore.handlers.ReplaceFileCorruptionHandler
-import kotlinx.coroutines.FlowPreview
-import kotlinx.coroutines.ObsoleteCoroutinesApi
-import kotlinx.coroutines.flow.first
-import kotlinx.coroutines.test.TestCoroutineScope
-import kotlinx.coroutines.test.runBlockingTest
-import org.junit.Before
-import org.junit.Rule
-import org.junit.Test
-import org.junit.rules.TemporaryFolder
-import java.io.File
-import kotlin.test.assertEquals
-
-@ObsoleteCoroutinesApi
-@kotlinx.coroutines.ExperimentalCoroutinesApi
-@FlowPreview
-class PreferenceDataStoreFactoryTest {
- @get:Rule
- val tmp = TemporaryFolder()
-
- private lateinit var testFile: File
- private lateinit var dataStoreScope: TestCoroutineScope
-
- @Before
- fun setUp() {
- testFile = tmp.newFile("test_file." + PreferencesSerializer.fileExtension)
- dataStoreScope = TestCoroutineScope()
- }
-
- @Test
- fun testNewInstance() = runBlockingTest {
- val factory = PreferenceDataStoreFactory()
- val store = factory.create(
- produceFile = { testFile },
- scope = dataStoreScope
- )
-
- val expectedPreferences = Preferences.Builder()
- .setString("key", "value")
- .build()
-
- assertEquals(store.updateData {
- it.toBuilder().setString("key", "value").build()
- }, expectedPreferences)
- assertEquals(expectedPreferences, store.data.first())
- }
-
- @Test
- fun testCorruptionHandlerInstalled() = runBlockingTest {
- testFile.writeBytes(byteArrayOf(0x00, 0x00, 0x00, 0x03)) // Protos can not start with 0x00.
-
- val factory = PreferenceDataStoreFactory()
-
- val valueToReplace = Preferences.Builder().setBoolean("key", true).build()
-
- val store = factory.create(
- produceFile = { testFile },
- corruptionHandler = ReplaceFileCorruptionHandler<Preferences> {
- valueToReplace
- },
- scope = dataStoreScope
- )
- assertEquals(valueToReplace, store.data.first())
- }
-
- @Test
- fun testMigrationsInstalled() = runBlockingTest {
- val factory = PreferenceDataStoreFactory()
-
- val expectedPreferences = Preferences.Builder()
- .setString("string_key", "value")
- .setBoolean("boolean_key", true)
- .build()
-
- val migrateTo5 = object : DataMigration<Preferences> {
- override suspend fun shouldMigrate(currentData: Preferences) = true
-
- override suspend fun migrate(currentData: Preferences) =
- currentData.toBuilder().setString("string_key", "value").build()
-
- override suspend fun cleanUp() {}
- }
-
- val migratePlus1 = object : DataMigration<Preferences> {
- override suspend fun shouldMigrate(currentData: Preferences) = true
-
- override suspend fun migrate(currentData: Preferences) =
- currentData.toBuilder().setBoolean("boolean_key", true).build()
-
- override suspend fun cleanUp() {}
- }
-
- val store = factory.create(
- produceFile = { testFile },
- migrations = listOf(migrateTo5, migratePlus1),
- scope = dataStoreScope
- )
-
- assertEquals(expectedPreferences, store.data.first())
- }
-}
\ No newline at end of file
diff --git a/datastore/datastore-preferences/src/test/java/androidx/datastore/preferences/PreferencesSerializerTest.kt b/datastore/datastore-preferences/src/test/java/androidx/datastore/preferences/PreferencesSerializerTest.kt
index 836f67b..89663b5 100644
--- a/datastore/datastore-preferences/src/test/java/androidx/datastore/preferences/PreferencesSerializerTest.kt
+++ b/datastore/datastore-preferences/src/test/java/androidx/datastore/preferences/PreferencesSerializerTest.kt
@@ -43,11 +43,11 @@
@Test
fun testWriteAndReadString() {
- val stringKey = "string_key"
+ val stringKey = preferencesKey<String>("string_key")
- val prefs = Preferences.Builder()
- .setString(stringKey, "string1")
- .build()
+ val prefs = preferencesOf(
+ stringKey to "string1"
+ )
testFile.outputStream().use {
preferencesSerializer.writeTo(prefs, it)
@@ -62,11 +62,11 @@
@Test
fun testWriteAndReadStringSet() {
- val stringSetKey = "string_set_key"
+ val stringSetKey = preferencesSetKey<String>("string_set_key")
- val prefs = Preferences.Builder()
- .setStringSet(stringSetKey, setOf("string1", "string2", "string3"))
- .build()
+ val prefs = preferencesOf(
+ stringSetKey to setOf("string1", "string2", "string3")
+ )
testFile.outputStream().use {
preferencesSerializer.writeTo(prefs, it)
@@ -81,11 +81,11 @@
@Test
fun testWriteAndReadLong() {
- val longKey = "long_key"
+ val longKey = preferencesKey<Long>("long_key")
- val prefs = Preferences.Builder()
- .setLong(longKey, 1 shr 50)
- .build()
+ val prefs = preferencesOf(
+ longKey to (1 shr 50)
+ )
testFile.outputStream().use {
preferencesSerializer.writeTo(prefs, it)
@@ -100,11 +100,11 @@
@Test
fun testWriteAndReadInt() {
- val intKey = "int_key"
+ val intKey = preferencesKey<Int>("int_key")
- val prefs = Preferences.Builder()
- .setInt(intKey, 3)
- .build()
+ val prefs = preferencesOf(
+ intKey to 3
+ )
testFile.outputStream().use {
preferencesSerializer.writeTo(prefs, it)
@@ -119,11 +119,11 @@
@Test
fun testWriteAndReadBoolean() {
- val booleanKey = "boolean_key"
+ val booleanKey = preferencesKey<Boolean>("boolean_key")
- val prefs = Preferences.Builder()
- .setBoolean(booleanKey, true)
- .build()
+ val prefs = preferencesOf(
+ booleanKey to true
+ )
testFile.outputStream().use {
preferencesSerializer.writeTo(prefs, it)
diff --git a/datastore/datastore-preferences/src/test/java/androidx/datastore/preferences/PreferencesTest.kt b/datastore/datastore-preferences/src/test/java/androidx/datastore/preferences/PreferencesTest.kt
index 542a2ee..4db3ea0 100644
--- a/datastore/datastore-preferences/src/test/java/androidx/datastore/preferences/PreferencesTest.kt
+++ b/datastore/datastore-preferences/src/test/java/androidx/datastore/preferences/PreferencesTest.kt
@@ -19,10 +19,12 @@
import org.junit.Test
import org.junit.runner.RunWith
import org.junit.runners.JUnit4
+import java.lang.IllegalArgumentException
+import java.lang.UnsupportedOperationException
import kotlin.test.assertEquals
import kotlin.test.assertFailsWith
-import kotlin.test.assertFalse
import kotlin.test.assertNotEquals
+import kotlin.test.assertNull
import kotlin.test.assertTrue
@RunWith(JUnit4::class)
@@ -30,185 +32,165 @@
@Test
fun testBoolean() {
- val booleanKey = "boolean_key"
+ val booleanKey = preferencesKey<Boolean>("boolean_key")
- val prefs = Preferences
- .empty()
- .toBuilder()
- .setBoolean(booleanKey, true)
- .build()
+ val prefs = preferencesOf(booleanKey to true)
- assertTrue { prefs.contains(booleanKey) }
- assertTrue { prefs.getBoolean(booleanKey, false) }
+ assertTrue { booleanKey in prefs }
+ assertTrue(prefs[booleanKey]!!)
}
@Test
- fun testBooleanDefault() {
- assertFalse(Preferences.empty().getBoolean("nonexistent key", false))
+ fun testBooleanNotSet() {
+ val booleanKey = preferencesKey<Boolean>("boolean_key")
+
+ assertNull(emptyPreferences()[booleanKey])
}
@Test
fun testFloat() {
- val floatKey = "float_key"
+ val floatKey = preferencesKey<Float>("float_key")
- val prefs = Preferences
- .empty()
- .toBuilder()
- .setFloat(floatKey, 1.1f)
- .build()
+ val prefs = preferencesOf(floatKey to 1.1f)
- assertTrue { prefs.contains(floatKey) }
- assertEquals(1.1f, prefs.getFloat(floatKey, 0.0f))
+ assertTrue { floatKey in prefs }
+ assertEquals(1.1f, prefs[floatKey])
}
@Test
- fun testFloatDefault() {
- assertEquals(0.1f, Preferences.empty().getFloat("nonexistent key", 0.1f))
+ fun testFloatNotSet() {
+ val floatKey = preferencesKey<Float>("float_key")
+ assertNull(emptyPreferences()[floatKey])
}
@Test
fun testInt() {
- val intKey = "int_key"
+ val intKey = preferencesKey<Int>("int_key")
- val prefs = Preferences
- .empty()
- .toBuilder()
- .setInt(intKey, 1)
- .build()
+ val prefs = preferencesOf(intKey to 1)
assertTrue { prefs.contains(intKey) }
- assertEquals(1, prefs.getInt(intKey, -1))
+ assertEquals(1, prefs[intKey])
}
@Test
- fun testIntDefault() {
- assertEquals(123, Preferences.empty().getInt("nonexistent key", 123))
+ fun testIntNotSet() {
+ val intKey = preferencesKey<Int>("int_key")
+ assertNull(emptyPreferences()[intKey])
}
@Test
fun testLong() {
- val longKey = "long_key"
+ val longKey = preferencesKey<Long>("long_key")
val bigLong = 1L shr 50; // 2^50 > Int.MAX_VALUE
- val prefs = Preferences
- .empty()
- .toBuilder()
- .setLong(longKey, bigLong)
- .build()
+ val prefs = preferencesOf(longKey to bigLong)
assertTrue { prefs.contains(longKey) }
- assertEquals(bigLong, prefs.getLong(longKey, -1))
+ assertEquals(bigLong, prefs[longKey])
}
@Test
- fun testLongDefault() {
- assertEquals(123, Preferences.empty().getLong("nonexistent key", 123))
+ fun testLongNotSet() {
+ val longKey = preferencesKey<Long>("long_key")
+
+ assertNull(emptyPreferences()[longKey])
}
@Test
fun testString() {
- val stringKey = "string_key"
+ val stringKey = preferencesKey<String>("string_key")
- val prefs = Preferences
- .empty()
- .toBuilder()
- .setString(stringKey, "string123")
- .build()
+ val prefs = preferencesOf(stringKey to "string123")
assertTrue { prefs.contains(stringKey) }
- assertEquals("string123", prefs.getString(stringKey, "default string"))
+ assertEquals("string123", prefs[stringKey])
}
@Test
- fun testStringDefault() {
- assertEquals("default val", Preferences.empty().getString("nonexistent key", "default val"))
+ fun testStringNotSet() {
+ val stringKey = preferencesKey<String>("string_key")
+
+ assertNull(emptyPreferences()[stringKey])
}
@Test
fun testStringSet() {
- val stringSetKey = "string_set_key"
+ val stringSetKey = preferencesSetKey<String>("string_set_key")
- val prefs = Preferences
- .empty()
- .toBuilder()
- .setStringSet(stringSetKey, setOf("string1", "string2", "string3"))
- .build()
+ val prefs = preferencesOf(stringSetKey to setOf("string1", "string2", "string3"))
assertTrue { prefs.contains(stringSetKey) }
assertEquals(
-
- setOf(
- "string1",
- "string2",
- "string3"
- ), prefs.getStringSet(stringSetKey, setOf())
+ setOf("string1", "string2", "string3"), prefs[stringSetKey]
)
}
@Test
- fun testStringSetDefault() {
- assertEquals(
- setOf("default set"), Preferences.empty().getStringSet(
- "nonexistent key", setOf("default set")
- )
- )
+ fun testStringSetNotSet() {
+ val stringSetKey = preferencesSetKey<String>("string_set_key")
+
+ assertNull(emptyPreferences()[stringSetKey])
}
@Test
fun testModifyingStringSetDoesntModifyInternalState() {
- val stringSetKey = "string_set_key"
+ val stringSetKey = preferencesSetKey<String>("string_set_key")
- val prefs = Preferences
- .empty()
- .toBuilder()
- .setStringSet(stringSetKey, setOf("string1", "string2", "string3"))
- .build()
+ val stringSet = mutableSetOf("1", "2", "3")
- val returnedSet: Set<String> = prefs.getStringSet(stringSetKey, setOf())
+ val prefs = preferencesOf(stringSetKey to stringSet)
+
+ stringSet.add("4") // modify the set passed into preferences
+
+ // modify the returned set.
+ val returnedSet: Set<String> = prefs[stringSetKey]!!
val mutableReturnedSet: MutableSet<String> = returnedSet as MutableSet<String>
- mutableReturnedSet.clear()
- mutableReturnedSet.add("Original set does not contain this string")
- assertEquals(
- setOf(
- "string1",
- "string2",
- "string3"
- ),
- prefs.getStringSet(stringSetKey, setOf())
- )
+ assertFailsWith<UnsupportedOperationException> {
+ mutableReturnedSet.clear()
+ }
+ assertFailsWith<UnsupportedOperationException> {
+ mutableReturnedSet.add("Original set does not contain this string")
+ }
+
+ assertEquals(setOf("1", "2", "3"), prefs[stringSetKey])
}
@Test
+ @Suppress("UNUSED_VARIABLE")
fun testWrongTypeThrowsClassCastException() {
- val stringKey = "string_key"
+ val stringKey = preferencesKey<String>("string_key")
+ val intKey = preferencesKey<Int>("string_key") // long key of the same name as stringKey!
+ val longKey = preferencesKey<Long>("string_key")
- val prefs = Preferences
- .empty()
- .toBuilder()
- .setString(stringKey, "string123")
- .build()
+ val prefs = preferencesOf(intKey to 123456)
- assertTrue { prefs.contains(stringKey) }
+ assertTrue { prefs.contains(intKey) }
+ assertTrue { prefs.contains(stringKey) } // TODO: I don't think we can prevent this
- // Trying to get a long where there is a string value throws a ClassCastException.
- assertFailsWith<ClassCastException> { prefs.getLong(stringKey, 123) }
+ // Trying to get a long where there is an Int value throws a ClassCastException.
+ assertFailsWith<ClassCastException> {
+ var unused = prefs[stringKey] // This only throws if it's assigned to a
+ // variable
+ }
+
+ // Trying to get a Long where there is an Int value throws a ClassCastException.
+ assertFailsWith<ClassCastException> {
+ var unused = prefs[longKey] // This only throws if it's assigned to a
+ // variable
+ }
}
@Test
fun testGetAll() {
- val intKey = "int_key"
- val stringSetKey = "string_set_key"
+ val intKey = preferencesKey<Int>("int_key")
+ val stringSetKey = preferencesSetKey<String>("string_set_key")
- val prefs = Preferences
- .empty()
- .toBuilder()
- .setInt(intKey, 123)
- .setStringSet(stringSetKey, setOf("1", "2", "3"))
- .build()
+ val prefs = preferencesOf(intKey to 123, stringSetKey to setOf("1", "2", "3"))
- val allPreferences = prefs.getAll()
+ val allPreferences: Map<Preferences.Key<*>, Any> = prefs.asMap()
assertEquals(2, allPreferences.size)
assertEquals(123, allPreferences[intKey])
@@ -218,126 +200,161 @@
@Test
@Suppress("UNCHECKED_CAST")
fun testGetAllCantMutateInternalState() {
- val intKey = "int_key"
- val stringSetKey = "string_set_key"
+ val intKey = preferencesKey<Int>("int_key")
+ val stringSetKey = preferencesSetKey<String>("string_set_key")
- val prefs = Preferences
- .empty()
- .toBuilder()
- .setInt(intKey, 123)
- .setStringSet(stringSetKey, setOf("1", "2", "3"))
- .build()
+ val prefs = preferencesOf(intKey to 123, stringSetKey to setOf("1", "2", "3"))
- val mutableAllPreferences = prefs.getAll() as MutableMap
- mutableAllPreferences[intKey] = 99999
- (mutableAllPreferences[stringSetKey] as MutableSet<String>).clear()
+ val mutableAllPreferences = prefs.asMap() as MutableMap
+ assertFailsWith<UnsupportedOperationException> {
+ mutableAllPreferences[intKey] = 99999
+ }
+ assertFailsWith<UnsupportedOperationException> {
+ (mutableAllPreferences[stringSetKey] as MutableSet<String>).clear()
+ }
- assertEquals(123, prefs.getInt(intKey, -1))
- assertEquals(setOf("1", "2", "3"), prefs.getStringSet(stringSetKey, setOf()))
+ assertEquals(123, prefs[intKey])
+ assertEquals(setOf("1", "2", "3"), prefs[stringSetKey])
}
@Test
- fun testBuilderClear() {
- val intKey = "int_key"
+ fun testMutablePreferencesClear() {
+ val intKey = preferencesKey<Int>("int_key")
- val prefsWithInt = Preferences
- .empty()
- .toBuilder()
- .setInt(intKey, 123)
- .build()
+ val prefsWithInt = preferencesOf(intKey to 123)
- val emptyPrefs = prefsWithInt.toBuilder().clear().build()
+ val emptyPrefs = prefsWithInt.toMutablePreferences().apply { clear() }.toPreferences()
- assertEquals(Preferences.empty(), emptyPrefs)
+ assertEquals(emptyPreferences(), emptyPrefs)
}
@Test
- fun testBuilderRemove() {
- val intKey = "int_key"
+ fun testMutablePreferencesRemove() {
+ val intKey = preferencesKey<Int>("int_key")
- val prefsWithInt = Preferences
- .empty()
- .toBuilder()
- .setInt(intKey, 123)
- .build()
+ val prefsWithInt = preferencesOf(intKey to 123)
- val emptyPrefs = prefsWithInt.toBuilder().remove(intKey).build()
+ val emptyPrefs =
+ prefsWithInt.toMutablePreferences().apply { remove(intKey) }.toPreferences()
- assertEquals(Preferences.empty(), emptyPrefs)
+ assertEquals(emptyPreferences(), emptyPrefs)
+
+ val emptyPrefs2 = prefsWithInt.toMutablePreferences()
+ emptyPrefs2 -= intKey
+
+ assertEquals(emptyPreferences(), emptyPrefs2)
}
@Test
fun testBuilderPublicConstructor() {
- val emptyPrefs = Preferences.Builder().build()
+ val emptyPrefs = mutablePreferencesOf().toPreferences()
- assertEquals(Preferences.empty(), emptyPrefs)
+ assertEquals(emptyPreferences(), emptyPrefs)
}
@Test
fun testEqualsDifferentInstances() {
- val intKey1 = "int_key1"
+ val intKey1 = preferencesKey<Int>("int_key1")
- val prefs1 = Preferences.empty().toBuilder().setInt(intKey1, 123).build()
- val prefs2 = Preferences.empty().toBuilder().setInt(intKey1, 123).build()
+ val prefs1 = preferencesOf(intKey1 to 123)
+ val prefs2 = preferencesOf(intKey1 to 123)
assertEquals(prefs1, prefs2)
}
@Test
fun testNotEqualsDifferentKeys() {
- val intKey1 = "int_key1"
- val intKey2 = "int_key2"
+ val intKey1 = preferencesKey<Int>("int_key1")
+ val intKey2 = preferencesKey<Int>("int_key2")
- val prefs1 = Preferences
- .empty()
- .toBuilder()
- .setInt(intKey1, 123)
- .build()
-
- val prefs2 = Preferences
- .empty()
- .toBuilder()
- .setInt(intKey2, 123)
- .build()
+ val prefs1 = preferencesOf(intKey1 to 123)
+ val prefs2 = preferencesOf(intKey2 to 123)
assertNotEquals(prefs1, prefs2)
}
@Test
fun testNotEqualsDifferentValues() {
- val intKey = "int_key"
+ val intKey1 = preferencesKey<Int>("int_key1")
- val prefs1 = Preferences
- .empty()
- .toBuilder()
- .setInt(intKey, 123)
- .build()
-
- val prefs2 = Preferences
- .empty()
- .toBuilder()
- .setInt(intKey, 999)
- .build()
+ val prefs1 = preferencesOf(intKey1 to 123)
+ val prefs2 = preferencesOf(intKey1 to 999)
assertNotEquals(prefs1, prefs2)
}
@Test
fun testNotEqualsDifferentStringSets() {
- val stringSetKey = "string_set_key"
+ val stringSetKey = preferencesSetKey<String>("string_set")
- val prefs1 = Preferences
- .empty()
- .toBuilder()
- .setStringSet(stringSetKey, setOf("string1", "string2"))
- .build()
-
- val prefs2 = Preferences
- .empty()
- .toBuilder()
- .setStringSet(stringSetKey, setOf("different string1", "string2"))
- .build()
+ val prefs1 = preferencesOf(stringSetKey to setOf("1"))
+ val prefs2 = preferencesOf(stringSetKey to setOf())
assertNotEquals(prefs1, prefs2)
}
+
+ @Test
+ fun testCreateUnsupportedKeyType_failsWithIllegalStateException() {
+ assertFailsWith<IllegalArgumentException> { preferencesKey<Set<String>>("test") }
+ assertFailsWith<IllegalArgumentException> { preferencesKey<Set<*>>("test") }
+ assertFailsWith<IllegalArgumentException> { preferencesKey<Double>("test") }
+ assertFailsWith<IllegalArgumentException> { preferencesKey<Any>("test") }
+ }
+
+ @Test
+ fun testCreateUnsupportedSetKeyType_failsWithIllegalStateException() {
+ assertFailsWith<IllegalArgumentException> { preferencesSetKey<Set<String>>("test") }
+ assertFailsWith<IllegalArgumentException> { preferencesSetKey<Set<*>>("test") }
+ assertFailsWith<IllegalArgumentException> { preferencesSetKey<Double>("test") }
+ assertFailsWith<IllegalArgumentException> { preferencesSetKey<Any>("test") }
+ }
+
+ @Test
+ fun testToPreferences_retainsAllKeys() {
+ val intKey1 = preferencesKey<Int>("int_key1")
+ val intKey2 = preferencesKey<Int>("int_key2")
+ val prefs = preferencesOf(intKey1 to 1, intKey2 to 2)
+ val toPrefs = prefs.toPreferences()
+ assertEquals( 2, toPrefs.asMap().size)
+ assertEquals(1, prefs[intKey1])
+ assertEquals(2, prefs[intKey2])
+
+ val mutablePreferences = preferencesOf(intKey1 to 1, intKey2 to 2)
+ val mutableToPrefs = mutablePreferences.toPreferences()
+ assertEquals( 2, mutableToPrefs.asMap().size)
+ assertEquals(1, prefs[intKey1])
+ assertEquals(2, prefs[intKey2])
+ }
+
+ @Test
+ fun testToMutablePreferences_retainsAllKeys() {
+ val intKey1 = preferencesKey<Int>("int_key1")
+ val intKey2 = preferencesKey<Int>("int_key2")
+ val prefs = preferencesOf(intKey1 to 1, intKey2 to 2)
+ val toPrefs = prefs.toMutablePreferences()
+ assertEquals( 2, toPrefs.asMap().size)
+ assertEquals(1, prefs[intKey1])
+ assertEquals(2, prefs[intKey2])
+
+ val mutablePreferences = preferencesOf(intKey1 to 1, intKey2 to 2)
+ val mutableToPrefs = mutablePreferences.toMutablePreferences()
+ assertEquals( 2, mutableToPrefs.asMap().size)
+ assertEquals(1, prefs[intKey1])
+ assertEquals(2, prefs[intKey2])
+ }
+
+ @Test
+ fun testToMutablePreferences_doesntMutateOriginal() {
+ val intKey1 = preferencesKey<Int>("int_key1")
+ val intKey2 = preferencesKey<Int>("int_key2")
+ val prefs = mutablePreferencesOf(intKey1 to 1, intKey2 to 2)
+ val toPrefs = prefs.toMutablePreferences()
+ toPrefs[intKey1] = 12903819
+ assertEquals(1, prefs[intKey1])
+
+ val mutablePreferences = preferencesOf(intKey1 to 1, intKey2 to 2)
+ val mutableToPrefs = mutablePreferences.toMutablePreferences()
+ mutableToPrefs[intKey1] = 12903819
+ assertEquals(1, prefs[intKey1])
+ }
}
\ No newline at end of file
diff --git a/datastore/datastore-sampleapp/build.gradle b/datastore/datastore-sampleapp/build.gradle
index ff065b1..6ab3c6d 100644
--- a/datastore/datastore-sampleapp/build.gradle
+++ b/datastore/datastore-sampleapp/build.gradle
@@ -60,7 +60,6 @@
api("androidx.preference:preference:1.1.0")
implementation(PROTOBUF_LITE)
-
implementation(KOTLIN_STDLIB)
implementation(MATERIAL)
diff --git a/datastore/datastore-sampleapp/src/main/java/com/example/datastoresampleapp/PreferencesDataStoreActivity.kt b/datastore/datastore-sampleapp/src/main/java/com/example/datastoresampleapp/PreferencesDataStoreActivity.kt
index d36e21b..6f68f5b 100644
--- a/datastore/datastore-sampleapp/src/main/java/com/example/datastoresampleapp/PreferencesDataStoreActivity.kt
+++ b/datastore/datastore-sampleapp/src/main/java/com/example/datastoresampleapp/PreferencesDataStoreActivity.kt
@@ -24,27 +24,29 @@
import androidx.annotation.Sampled
import androidx.appcompat.app.AppCompatActivity
import androidx.datastore.DataStore
-import androidx.datastore.preferences.PreferenceDataStoreFactory
+import androidx.datastore.createDataStore
import androidx.datastore.preferences.Preferences
+import androidx.datastore.preferences.createDataStore
+import androidx.datastore.preferences.edit
+import androidx.datastore.preferences.emptyPreferences
+import androidx.datastore.preferences.preferencesKey
import androidx.lifecycle.lifecycleScope
import kotlinx.coroutines.flow.catch
import kotlinx.coroutines.flow.collect
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.launch
-import java.io.File
import java.io.IOException
@OptIn(kotlinx.coroutines.ExperimentalCoroutinesApi::class)
class PreferencesDataStoreActivity : AppCompatActivity() {
private val TAG = "PreferencesActivity"
- private val PREFERENCE_STORE_FILE_NAME = "datastore_test_app.preferences_pb"
- private val COUNTER_KEY = "counter"
+ private val PREFERENCE_STORE_FILE_NAME = "datastore_test_app"
+ private val COUNTER_KEY = preferencesKey<Int>("counter")
private val preferenceStore: DataStore<Preferences> by lazy {
- PreferenceDataStoreFactory().create(
- { File(applicationContext.filesDir, PREFERENCE_STORE_FILE_NAME) })
+ applicationContext.createDataStore(PREFERENCE_STORE_FILE_NAME)
}
@Sampled
@@ -69,18 +71,16 @@
// Using preferenceStore:
findViewById<Button>(R.id.counter_dec).setOnClickListener {
lifecycleScope.launch {
- preferenceStore.updateData { currentPrefs ->
- val currentValue = currentPrefs.getInt(COUNTER_KEY, defaultValue = 0)
- currentPrefs.toBuilder().setInt(COUNTER_KEY, currentValue - 1).build()
+ preferenceStore.edit { prefs ->
+ prefs[COUNTER_KEY] = prefs[COUNTER_KEY] ?: 0 - 1
}
}
}
findViewById<Button>(R.id.counter_inc).setOnClickListener {
lifecycleScope.launch {
- preferenceStore.updateData { currentPrefs ->
- val currentValue = currentPrefs.getInt(COUNTER_KEY, defaultValue = 0)
- currentPrefs.toBuilder().setInt(COUNTER_KEY, currentValue + 1).build()
+ preferenceStore.edit { prefs ->
+ prefs[COUNTER_KEY] = prefs[COUNTER_KEY] ?: 0 + 1
}
}
}
@@ -90,12 +90,12 @@
.catch { e ->
if (e is IOException) {
Log.e(TAG, "Error reading preferences.", e)
- emit(Preferences.empty())
+ emit(emptyPreferences())
} else {
throw e
}
}
- .map { it.getInt(COUNTER_KEY, defaultValue = 0) }
+ .map { it[COUNTER_KEY] ?: 0 }
.distinctUntilChanged()
.collect { counterValue ->
findViewById<TextView>(R.id.counter_text_view).text = counterValue.toString()
diff --git a/datastore/datastore-sampleapp/src/main/java/com/example/datastoresampleapp/ProtoDataStoreActivity.kt b/datastore/datastore-sampleapp/src/main/java/com/example/datastoresampleapp/ProtoDataStoreActivity.kt
index 57d6411..8b46d26 100644
--- a/datastore/datastore-sampleapp/src/main/java/com/example/datastoresampleapp/ProtoDataStoreActivity.kt
+++ b/datastore/datastore-sampleapp/src/main/java/com/example/datastoresampleapp/ProtoDataStoreActivity.kt
@@ -46,7 +46,7 @@
private val PROTO_STORE_FILE_NAME = "datastore_test_app.pb"
private val settingsStore: DataStore<Settings> by lazy {
- DataStoreFactory().create(
+ DataStoreFactory.create(
{ File(applicationContext.filesDir, PROTO_STORE_FILE_NAME) },
SettingsSerializer
)
diff --git a/datastore/datastore-sampleapp/src/main/java/com/example/datastoresampleapp/SettingsFragment.kt b/datastore/datastore-sampleapp/src/main/java/com/example/datastoresampleapp/SettingsFragment.kt
index cd4966f..b2e8d2b 100644
--- a/datastore/datastore-sampleapp/src/main/java/com/example/datastoresampleapp/SettingsFragment.kt
+++ b/datastore/datastore-sampleapp/src/main/java/com/example/datastoresampleapp/SettingsFragment.kt
@@ -69,7 +69,7 @@
private val PROTO_STORE_FILE_NAME = "datastore_test_app.pb"
private val settingsStore: DataStore<Settings> by lazy {
- DataStoreFactory().create(
+ DataStoreFactory.create(
{ File(requireActivity().applicationContext.filesDir, PROTO_STORE_FILE_NAME) },
SettingsSerializer
)
diff --git a/development/diagnose-build-failure/diagnose-build-failure.sh b/development/diagnose-build-failure/diagnose-build-failure.sh
index 1ca2b1d..bce19c8 100755
--- a/development/diagnose-build-failure/diagnose-build-failure.sh
+++ b/development/diagnose-build-failure/diagnose-build-failure.sh
@@ -65,6 +65,7 @@
fi
scriptPath="$(cd $(dirname $0) && pwd)"
+vgrep="$scriptPath/impl/vgrep.sh"
supportRoot="$(cd $scriptPath/../.. && pwd)"
checkoutRoot="$(cd $supportRoot/../.. && pwd)"
tempDir="$checkoutRoot/diagnose-build-failure/"
@@ -92,29 +93,28 @@
fi
}
-function runBuild() {
+function getBuildComand() {
if [ "$expectedMessage" == "" ]; then
- echo -e "$COLOR_GREEN"
testCommand="$*"
- returnOnSuccess=0
- returnOnFailure=1
else
- testCommand="$* 2>&1 | grep '$expectedMessage'"
- # invert the return value because the presence of this message indicates a problem in the build
- returnOnSuccess=1
- returnOnFailure=0
+ testCommand="$* 2>&1 | $vgrep '$expectedMessage'"
fi
+ echo "$testCommand"
+}
+
+function runBuild() {
+ testCommand="$(getBuildCommand $*)"
cd "$workingDir"
if eval $testCommand; then
echo -e "$COLOR_WHITE"
echo
echo '`'$testCommand'`' succeeded
- return $returnOnSuccess
+ return 0
else
echo -e "$COLOR_WHITE"
echo
echo '`'$testCommand'`' failed
- return $returnOnFailure
+ return 1
fi
}
@@ -230,7 +230,8 @@
echo "Binary-searching the contents of the two output directories until the relevant differences are identified."
echo "This may take a while."
echo
-if runBuild "$supportRoot/development/file-utils/diff-filterer.py --assume-no-side-effects --assume-input-states-are-correct --work-path $tempDir $successState $tempDir/prev \"$scriptPath/impl/restore-state.sh . $workingDir && cd $workingDir && ./gradlew --no-daemon $gradleArgs\""; then
+filtererCommand="$(getBuildCommand \"$scriptPath/impl/restore-state.sh . $workingDir && cd $workingDir && ./gradlew --no-daemon $gradleArgs\")"
+if $supportRoot/development/file-utils/diff-filterer.py --assume-no-side-effects --assume-input-states-are-correct --work-path $tempDir $successState $tempDir/prev "$filtererCommand"; then
echo
echo "There should be something wrong with the above file state"
echo "Hopefully the output from diff-filterer.py above is enough information for you to figure out what is wrong"
diff --git a/development/diagnose-build-failure/impl/vgrep.sh b/development/diagnose-build-failure/impl/vgrep.sh
new file mode 100755
index 0000000..d3acb8f
--- /dev/null
+++ b/development/diagnose-build-failure/impl/vgrep.sh
@@ -0,0 +1,14 @@
+#!/bin/bash
+set -e
+
+query="$1"
+if [ "$query" == "" ]; then
+ echo "Usage: vgrep.sh <query>"
+ echo "Runs grep and inverts the return code"
+ exit 2
+fi
+
+if grep "$1"; then
+ exit 1
+fi
+exit 0
diff --git a/development/file-utils/diff-filterer.py b/development/file-utils/diff-filterer.py
index 776c20f..5be0de5 100755
--- a/development/file-utils/diff-filterer.py
+++ b/development/file-utils/diff-filterer.py
@@ -138,7 +138,7 @@
def apply(self, filePath):
pass
- def equals(self, other):
+ def equals(self, other, checkWithFileSystem=False):
pass
# A FileContent that refers to the content of a specific file
@@ -151,11 +151,13 @@
def apply(self, filePath):
fileIo.copyFile(self.referencePath, filePath)
- def equals(self, other):
+ def equals(self, other, checkWithFileSystem=False):
if not isinstance(other, FileBacked_FileContent):
return False
if self.referencePath == other.referencePath:
return True
+ if not checkWithFileSystem:
+ return False
if self.isLink and other.isLink:
return os.readlink(self.referencePath) == os.readlink(other.referencePath)
if self.isLink != other.isLink:
@@ -173,7 +175,7 @@
def apply(self, filePath):
fileIo.removePath(filePath)
- def equals(self, other):
+ def equals(self, other, checkWithFileSystem=False):
return isinstance(other, MissingFile_FileContent)
def __str__(self):
@@ -187,7 +189,7 @@
def apply(self, filePath):
fileIo.ensureDirExists(filePath)
- def equals(self, other):
+ def equals(self, other, checkWithFileSystem=False):
return isinstance(other, Directory_FileContent)
def __str__(self):
@@ -214,18 +216,12 @@
return self.fileStates[filePath]
return None
- def hasContentAt(self, filePath, content):
- ourContent = self.getContent(filePath)
- if ourContent is None:
- return (content is None)
- return ourContent.equals(content)
-
# returns a FilesState resembling <self> but without the keys for which other[key] == self[key]
- def withoutDuplicatesFrom(self, other):
+ def withoutDuplicatesFrom(self, other, checkWithFileSystem=False):
result = FilesState()
for filePath, fileState in self.fileStates.iteritems():
otherContent = other.getContent(filePath)
- if not fileState.equals(otherContent):
+ if not fileState.equals(otherContent, checkWithFileSystem):
result.add(filePath, fileState)
return result
@@ -711,8 +707,8 @@
# list of the files in the state to reset to after each test
self.full_resetTo_state = self.originalPassingState
# minimal description of only the files that are supposed to need to be reset after each test
- self.resetTo_state = self.originalPassingState.expandedWithEmptyEntriesFor(self.originalFailingState).withoutDuplicatesFrom(self.originalFailingState)
- self.targetState = self.originalFailingState.expandedWithEmptyEntriesFor(self.originalPassingState).withoutDuplicatesFrom(self.originalPassingState)
+ self.resetTo_state = self.originalPassingState.expandedWithEmptyEntriesFor(self.originalFailingState).withoutDuplicatesFrom(self.originalFailingState, True)
+ self.targetState = self.originalFailingState.expandedWithEmptyEntriesFor(self.originalPassingState).withoutDuplicatesFrom(self.originalPassingState, True)
self.originalNumDifferences = self.resetTo_state.size()
print("Processing " + str(self.originalNumDifferences) + " file differences")
self.maxNumJobsAtOnce = maxNumJobsAtOnce
diff --git a/development/importMaven/build.gradle.kts b/development/importMaven/build.gradle.kts
index 1542f76..be0e69c 100644
--- a/development/importMaven/build.gradle.kts
+++ b/development/importMaven/build.gradle.kts
@@ -15,9 +15,10 @@
*/
import okhttp3.MediaType
+import okhttp3.MediaType.Companion.toMediaType
import okhttp3.OkHttpClient
import okhttp3.Request
-import okhttp3.RequestBody
+import okhttp3.RequestBody.Companion.toRequestBody
import org.w3c.dom.Element
import org.w3c.dom.Node
import java.security.MessageDigest
@@ -37,7 +38,7 @@
dependencies {
classpath("org.apache.maven:maven-model:3.5.4")
classpath("org.apache.maven:maven-model-builder:3.5.4")
- classpath("com.squareup.okhttp3:okhttp:3.14.7")
+ classpath("com.squareup.okhttp3:okhttp:4.8.1")
classpath("javax.inject:javax.inject:1")
}
}
@@ -48,7 +49,7 @@
val externalFolder = "external"
// Passed in as a project property
val artifactName = project.findProperty("artifactName")
-val mediaType = MediaType.get("application/json; charset=utf-8")
+val mediaType = "application/json; charset=utf-8".toMediaType()
val licenseEndpoint = "https://fetch-licenses.appspot.com/convert/licenses"
val internalArtifacts = listOf(
@@ -127,6 +128,15 @@
}
}
+val gradleModuleMetadata: Configuration by configurations.creating {
+ attributes {
+ // We define this attribute in DirectMetadataAccessVariantRule
+ attribute(Category.CATEGORY_ATTRIBUTE, objects.named(Category.DOCUMENTATION) )
+ attribute(DocsType.DOCS_TYPE_ATTRIBUTE, objects.named("gradle-module-metadata"))
+ }
+ extendsFrom(configurations.runtimeClasspath.get())
+}
+
val allFilesWithDependencies: Configuration by configurations.creating {
attributes {
// We define this attribute in DirectMetadataAccessVariantRule
@@ -220,10 +230,10 @@
val element = children.item(j)
if (element.nodeName.toLowerCase() == "url") {
val url = element.textContent
- val payload = RequestBody.create(mediaType, "{\"url\": \"$url\"}")
+ val payload = "{\"url\": \"$url\"}".toRequestBody(mediaType)
val request = Request.Builder().url(licenseEndpoint).post(payload).build()
val response = client.newCall(request).execute()
- val contents = response.body()?.string()
+ val contents = response.body?.string()
if (contents != null) {
val parent = System.getProperty("java.io.tmpdir")
val outputFile = File(parent, "${pomFile.name}.LICENSE")
@@ -404,12 +414,21 @@
*/
@CacheableRule
open class DirectMetadataAccessVariantRule : ComponentMetadataRule {
-
@javax.inject.Inject
open fun getObjects(): ObjectFactory = throw UnsupportedOperationException()
override fun execute(ctx: ComponentMetadataContext) {
val id = ctx.details.id
+ ctx.details.addVariant("moduleMetadata") {
+ attributes {
+ attribute(Usage.USAGE_ATTRIBUTE, getObjects().named(Usage.JAVA_RUNTIME))
+ attribute(Category.CATEGORY_ATTRIBUTE, getObjects().named(Category.DOCUMENTATION))
+ attribute(DocsType.DOCS_TYPE_ATTRIBUTE, getObjects().named("gradle-module-metadata"))
+ }
+ withFiles {
+ addFile("${id.name}-${id.version}.module")
+ }
+ }
ctx.details.maybeAddVariant("allFilesWithDependenciesElements", "runtimeElements") {
attributes {
attribute(Usage.USAGE_ATTRIBUTE, getObjects().named(Usage.JAVA_RUNTIME))
@@ -457,7 +476,11 @@
}.artifacts.forEach {
copyArtifact(it, internal = isInternalArtifact(it))
}
-
+ gradleModuleMetadata.incoming.artifactView {
+ lenient(true)
+ }.artifacts.forEach {
+ copyArtifact(it, internal = isInternalArtifact(it))
+ }
println("\r\nResolved artifacts for $artifactName.")
}
}
diff --git a/development/referenceDocs/stageComposeReferenceDocs.sh b/development/referenceDocs/stageComposeReferenceDocs.sh
index 37c691a..e7fd010 100755
--- a/development/referenceDocs/stageComposeReferenceDocs.sh
+++ b/development/referenceDocs/stageComposeReferenceDocs.sh
@@ -60,8 +60,6 @@
sed -i "s/path: \/reference\/kotlin\/androidx\/packages.html/path: \/reference\/kotlin\/androidx\/ui\/packages.html/g" reference/kotlin/androidx/ui/_toc.yaml
sed -i "s/href=\"animation\/package-summary.html\">androidx.animation/href=\"..\/animation\/package-summary.html\">androidx.animation/g" reference/kotlin/androidx/ui/packages.html
-grep -rl "{% setvar book_path %}/reference/kotlin/androidx/_book.yaml{% endsetvar %}" reference/kotlin/androidx/animation | xargs sed -i "s/{% setvar book_path %}\/reference\/kotlin\/androidx\/_book.yaml{% endsetvar %}/{% setvar book_path %}\/reference\/kotlin\/androidx\/ui\/_book.yaml{% endsetvar %}/g"
-
grep -rl "{% setvar book_path %}/reference/kotlin/androidx/_book.yaml{% endsetvar %}" reference/kotlin/androidx/compose | xargs sed -i "s/{% setvar book_path %}\/reference\/kotlin\/androidx\/_book.yaml{% endsetvar %}/{% setvar book_path %}\/reference\/kotlin\/androidx\/ui\/_book.yaml{% endsetvar %}/g"
grep -rl "{% setvar book_path %}/reference/kotlin/androidx/_book.yaml{% endsetvar %}" reference/kotlin/androidx/ui | xargs sed -i "s/{% setvar book_path %}\/reference\/kotlin\/androidx\/_book.yaml{% endsetvar %}/{% setvar book_path %}\/reference\/kotlin\/androidx\/ui\/_book.yaml{% endsetvar %}/g"
diff --git a/development/referenceDocs/stageReferenceDocs.sh b/development/referenceDocs/stageReferenceDocs.sh
index 0f30f80..ce557d6 100755
--- a/development/referenceDocs/stageReferenceDocs.sh
+++ b/development/referenceDocs/stageReferenceDocs.sh
@@ -107,7 +107,7 @@
g4d -f androidx-ref-docs-stage && \
cd third_party/devsite/android/en/reference && \
g4 sync && \
-cp -r ~/$(dirname $0)/out/$newDir/reference/* . && \
+cp -r $(pwd)/* && \
/google/data/ro/projects/devsite/two/live/devsite2.par stage androidx && \
/google/data/ro/projects/devsite/two/live/devsite2.par stage kotlin/androidx
\`\`\`\n"
diff --git a/fragment/fragment-ktx/build.gradle b/fragment/fragment-ktx/build.gradle
index 29be37f..9932a5c 100644
--- a/fragment/fragment-ktx/build.gradle
+++ b/fragment/fragment-ktx/build.gradle
@@ -40,6 +40,9 @@
because 'Mirror fragment dependency graph for -ktx artifacts'
}
api(project(":lifecycle:lifecycle-viewmodel-ktx"))
+ api(project(":savedstate:savedstate-ktx")) {
+ because 'Mirror fragment dependency graph for -ktx artifacts'
+ }
api(KOTLIN_STDLIB)
androidTestImplementation(JUNIT)
androidTestImplementation(TRUTH)
diff --git a/fragment/fragment/build.gradle b/fragment/fragment/build.gradle
index 0e272dc..83e2701 100644
--- a/fragment/fragment/build.gradle
+++ b/fragment/fragment/build.gradle
@@ -41,7 +41,7 @@
api(project(":lifecycle:lifecycle-livedata-core"))
api(project(":lifecycle:lifecycle-viewmodel"))
api(project(":lifecycle:lifecycle-viewmodel-savedstate"))
- api("androidx.savedstate:savedstate:1.1.0-alpha01")
+ api(project(":savedstate:savedstate"))
api("androidx.annotation:annotation-experimental:1.0.0")
androidTestImplementation("androidx.appcompat:appcompat:1.1.0", {
diff --git a/fragment/fragment/src/androidTest/java/androidx/fragment/app/FragmentAnimationTest.kt b/fragment/fragment/src/androidTest/java/androidx/fragment/app/FragmentAnimationTest.kt
index ebad1ec..30dae7d 100644
--- a/fragment/fragment/src/androidTest/java/androidx/fragment/app/FragmentAnimationTest.kt
+++ b/fragment/fragment/src/androidTest/java/androidx/fragment/app/FragmentAnimationTest.kt
@@ -761,12 +761,8 @@
private fun assertPostponed(fragment: AnimationFragment, expectedAnimators: Int) {
assertThat(fragment.onCreateViewCalled).isTrue()
- if (FragmentManager.USE_STATE_MANAGER) {
- assertThat(fragment.requireView().visibility).isEqualTo(View.INVISIBLE)
- } else {
- assertThat(fragment.requireView().visibility).isEqualTo(View.VISIBLE)
- assertThat(fragment.requireView().alpha).isWithin(0f).of(0f)
- }
+ assertThat(fragment.requireView().visibility).isEqualTo(View.VISIBLE)
+ assertThat(fragment.requireView().alpha).isWithin(0f).of(0f)
assertThat(fragment.numAnimators).isEqualTo(expectedAnimators)
}
diff --git a/fragment/fragment/src/androidTest/java/androidx/fragment/app/FragmentAnimatorTest.kt b/fragment/fragment/src/androidTest/java/androidx/fragment/app/FragmentAnimatorTest.kt
index 86d750e..5808f88 100644
--- a/fragment/fragment/src/androidTest/java/androidx/fragment/app/FragmentAnimatorTest.kt
+++ b/fragment/fragment/src/androidTest/java/androidx/fragment/app/FragmentAnimatorTest.kt
@@ -636,12 +636,8 @@
private fun assertPostponed(fragment: AnimatorFragment, expectedAnimators: Int) {
assertThat(fragment.onCreateViewCalled).isTrue()
- if (FragmentManager.USE_STATE_MANAGER) {
- assertThat(fragment.requireView().visibility).isEqualTo(View.INVISIBLE)
- } else {
- assertThat(fragment.requireView().visibility).isEqualTo(View.VISIBLE)
- assertThat(fragment.requireView().alpha).isWithin(0f).of(0f)
- }
+ assertThat(fragment.requireView().visibility).isEqualTo(View.VISIBLE)
+ assertThat(fragment.requireView().alpha).isWithin(0f).of(0f)
assertThat(fragment.numAnimators).isEqualTo(expectedAnimators)
}
diff --git a/fragment/fragment/src/androidTest/java/androidx/fragment/app/FragmentLifecycleTest.kt b/fragment/fragment/src/androidTest/java/androidx/fragment/app/FragmentLifecycleTest.kt
index d9dd33d..28cc10b 100644
--- a/fragment/fragment/src/androidTest/java/androidx/fragment/app/FragmentLifecycleTest.kt
+++ b/fragment/fragment/src/androidTest/java/androidx/fragment/app/FragmentLifecycleTest.kt
@@ -287,12 +287,16 @@
.commitNow()
assertThat(fragment.lifecycle.currentState).isEqualTo(Lifecycle.State.RESUMED)
+ val view = fragment.requireView()
+ assertThat(view.parent).isNotNull()
fm.beginTransaction()
.setMaxLifecycle(fragment, Lifecycle.State.CREATED)
.commitNow()
assertThat(fragment.lifecycle.currentState).isEqualTo(Lifecycle.State.CREATED)
+ assertThat(fragment.view).isNull()
+ assertThat(view.parent).isNull()
}
@Test
diff --git a/fragment/fragment/src/androidTest/java/androidx/fragment/app/FragmentViewTest.kt b/fragment/fragment/src/androidTest/java/androidx/fragment/app/FragmentViewTest.kt
index 941aefa..e534679 100644
--- a/fragment/fragment/src/androidTest/java/androidx/fragment/app/FragmentViewTest.kt
+++ b/fragment/fragment/src/androidTest/java/androidx/fragment/app/FragmentViewTest.kt
@@ -19,12 +19,16 @@
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
+import androidx.fragment.app.Fragment.STARTED
+import androidx.fragment.app.Fragment.VIEW_CREATED
import androidx.fragment.app.test.FragmentTestActivity
import androidx.fragment.test.R
import androidx.lifecycle.Lifecycle
+import androidx.test.core.app.ActivityScenario
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import androidx.test.platform.app.InstrumentationRegistry
+import androidx.testutils.withActivity
import com.google.common.truth.Truth.assertThat
import com.google.common.truth.Truth.assertWithMessage
import org.junit.Assert.fail
@@ -907,6 +911,38 @@
assertThat(fragment2.requireView().visibility).isEqualTo(View.GONE)
}
+ // Test that adding a fragment and making its view invisible in onStart is still invisible
+ @Test
+ fun makeFragmentInvisibleInOnStart() {
+ with(ActivityScenario.launch(FragmentTestActivity::class.java)) {
+ val container = withActivity { findViewById<View>(R.id.content) as ViewGroup }
+
+ val fm = withActivity { supportFragmentManager }
+
+ val fragment1 = InvisibleFragment(STARTED)
+ fm.beginTransaction()
+ .add(R.id.content, fragment1)
+ .addToBackStack(null)
+ .commit()
+ executePendingTransactions()
+
+ assertChildren(container, fragment1)
+
+ assertThat(fragment1.requireView().visibility).isEqualTo(View.INVISIBLE)
+
+ val fragment2 = InvisibleFragment()
+ fragment2.visibility = View.GONE
+ fm.beginTransaction()
+ .replace(R.id.content, fragment2)
+ .addToBackStack(null)
+ .commit()
+ executePendingTransactions()
+ assertChildren(container, fragment2)
+
+ assertThat(fragment2.requireView().visibility).isEqualTo(View.GONE)
+ }
+ }
+
// Test to ensure that popping and adding a fragment properly track the fragments added
// and removed.
@Test
@@ -1050,13 +1086,22 @@
return activityRule.activity.findViewById(viewId)
}
- class InvisibleFragment : StrictViewFragment() {
+ class InvisibleFragment(val state: Int = VIEW_CREATED) : StrictViewFragment() {
var visibility = View.INVISIBLE
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
- view.visibility = visibility
+ if (state == VIEW_CREATED) {
+ view.visibility = visibility
+ }
super.onViewCreated(view, savedInstanceState)
}
+
+ override fun onStart() {
+ if (state == STARTED) {
+ view?.visibility = visibility
+ }
+ super.onStart()
+ }
}
class ParentFragment : StrictViewFragment(R.layout.double_container) {
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 48a79752..fdae2f2 100644
--- a/fragment/fragment/src/androidTest/java/androidx/fragment/app/PostponedTransitionTest.kt
+++ b/fragment/fragment/src/androidTest/java/androidx/fragment/app/PostponedTransitionTest.kt
@@ -145,6 +145,42 @@
fragment, beginningFragment)
}
+ @Test
+ fun changePostponedFragmentVisibility() {
+ if (stateManager == OldStateManager) {
+ return
+ }
+ val fm = activityRule.activity.supportFragmentManager
+ val startBlue = activityRule.findBlue()
+
+ val fragment = PostponedFragment1()
+ fm.beginTransaction()
+ .addSharedElement(startBlue, "blueSquare")
+ .replace(R.id.fragmentContainer, fragment)
+ .addToBackStack(null)
+ .setReorderingAllowed(true)
+ .commit()
+
+ activityRule.waitForExecution()
+
+ fragment.requireView().visibility = View.INVISIBLE
+
+ activityRule.waitForExecution(1)
+
+ // should be postponed now
+ assertPostponedTransition(beginningFragment, fragment, toFragmentVisible = false)
+
+ // should be invisible
+ assertThat(fragment.requireView().visibility).isEqualTo(View.INVISIBLE)
+
+ // start the postponed transition
+ fragment.startPostponedEnterTransition()
+
+ // nothing should run since the fragment is INVISIBLE
+ verifyNoOtherTransitions(beginningFragment)
+ verifyNoOtherTransitions(fragment)
+ }
+
// Ensure that replacing a fragment doesn't cause problems with the back stack nesting level
@Test
fun backStackNestingLevel() {
@@ -1144,7 +1180,8 @@
private fun assertPostponedTransition(
fromFragment: TransitionFragment,
toFragment: TransitionFragment,
- removedFragment: TransitionFragment? = null
+ removedFragment: TransitionFragment? = null,
+ toFragmentVisible: Boolean = true
) {
if (removedFragment != null) {
assertThat(removedFragment.view).isNull()
@@ -1157,13 +1194,13 @@
assertThat(fromFragment.requireView().isAttachedToWindow).isTrue()
assertThat(toFragment.requireView().isAttachedToWindow).isTrue()
assertThat(fromFragment.requireView().visibility).isEqualTo(View.VISIBLE)
-
- if (stateManager is NewStateManager) {
- assertThat(toFragment.requireView().visibility).isEqualTo(View.INVISIBLE)
- } else {
+ if (toFragmentVisible) {
assertThat(toFragment.requireView().visibility).isEqualTo(View.VISIBLE)
- assertThat(toFragment.requireView().alpha).isWithin(0f).of(0f)
+ } else {
+ assertThat(toFragment.requireView().visibility).isEqualTo(View.INVISIBLE)
}
+ assertThat(toFragment.requireView().alpha).isWithin(0f).of(0f)
+
verifyNoOtherTransitions(fromFragment)
verifyNoOtherTransitions(toFragment)
if (stateManager is NewStateManager) {
diff --git a/fragment/fragment/src/main/java/androidx/fragment/app/Fragment.java b/fragment/fragment/src/main/java/androidx/fragment/app/Fragment.java
index c7530f6..ea8735a 100644
--- a/fragment/fragment/src/main/java/androidx/fragment/app/Fragment.java
+++ b/fragment/fragment/src/main/java/androidx/fragment/app/Fragment.java
@@ -3338,15 +3338,15 @@
return mAnimationInfo.mAnimator;
}
- void setPostOnViewCreatedVisibility(int visibility) {
- ensureAnimationInfo().mPostOnViewCreatedVisibility = visibility;
+ void setPostOnViewCreatedAlpha(float alpha) {
+ ensureAnimationInfo().mPostOnViewCreatedAlpha = alpha;
}
- int getPostOnViewCreatedVisibility() {
+ float getPostOnViewCreatedAlpha() {
if (mAnimationInfo == null) {
- return View.VISIBLE;
+ return 1f;
}
- return mAnimationInfo.mPostOnViewCreatedVisibility;
+ return mAnimationInfo.mPostOnViewCreatedAlpha;
}
void setFocusedView(View view) {
@@ -3533,7 +3533,7 @@
SharedElementCallback mEnterTransitionCallback = null;
SharedElementCallback mExitTransitionCallback = null;
- int mPostOnViewCreatedVisibility = View.VISIBLE;
+ float mPostOnViewCreatedAlpha = 1f;
View mFocusedView = null;
// True when postponeEnterTransition has been called and startPostponeEnterTransition
diff --git a/fragment/fragment/src/main/java/androidx/fragment/app/FragmentStateManager.java b/fragment/fragment/src/main/java/androidx/fragment/app/FragmentStateManager.java
index 00c1d0b..5ae158ad 100644
--- a/fragment/fragment/src/main/java/androidx/fragment/app/FragmentStateManager.java
+++ b/fragment/fragment/src/main/java/androidx/fragment/app/FragmentStateManager.java
@@ -176,6 +176,24 @@
// Assume the Fragment can go as high as the FragmentManager's state
int maxState = mFragmentManagerState;
+ // Don't allow the Fragment to go above its max lifecycle state
+ switch (mFragment.mMaxState) {
+ case RESUMED:
+ // maxState can't go any higher than RESUMED, so there's nothing to do here
+ break;
+ case STARTED:
+ maxState = Math.min(maxState, Fragment.STARTED);
+ break;
+ case CREATED:
+ maxState = Math.min(maxState, Fragment.CREATED);
+ break;
+ case INITIALIZED:
+ maxState = Math.min(maxState, Fragment.ATTACHED);
+ break;
+ default:
+ maxState = Math.min(maxState, Fragment.INITIALIZING);
+ }
+
// For fragments that are created from a layout using the <fragment> tag (mFromLayout)
if (mFragment.mFromLayout) {
if (mFragment.mInLayout) {
@@ -227,23 +245,6 @@
if (mFragment.mDeferStart && mFragment.mState < Fragment.STARTED) {
maxState = Math.min(maxState, Fragment.ACTIVITY_CREATED);
}
- // Don't allow the Fragment to go above its max lifecycle state
- switch (mFragment.mMaxState) {
- case RESUMED:
- // maxState can't go any higher than RESUMED, so there's nothing to do here
- break;
- case STARTED:
- maxState = Math.min(maxState, Fragment.STARTED);
- break;
- case CREATED:
- maxState = Math.min(maxState, Fragment.CREATED);
- break;
- case INITIALIZED:
- maxState = Math.min(maxState, Fragment.ATTACHED);
- break;
- default:
- maxState = Math.min(maxState, Fragment.INITIALIZING);
- }
return maxState;
}
@@ -299,7 +300,7 @@
mHiddenAnimationCancellationSignal.cancel();
}
mEnterAnimationCancellationSignal = new CancellationSignal();
- int visibility = mFragment.getPostOnViewCreatedVisibility();
+ int visibility = mFragment.mView.getVisibility();
SpecialEffectsController.Operation.State finalState =
SpecialEffectsController.Operation.State.from(visibility);
controller.enqueueAdd(finalState, this,
@@ -575,13 +576,14 @@
mDispatcher.dispatchOnFragmentViewCreated(
mFragment, mFragment.mView, mFragment.mSavedFragmentState, false);
int postOnViewCreatedVisibility = mFragment.mView.getVisibility();
+ float postOnViewCreatedAlpha = mFragment.mView.getAlpha();
if (FragmentManager.USE_STATE_MANAGER) {
- mFragment.setPostOnViewCreatedVisibility(postOnViewCreatedVisibility);
+ mFragment.setPostOnViewCreatedAlpha(postOnViewCreatedAlpha);
if (mFragment.mContainer != null && postOnViewCreatedVisibility == View.VISIBLE) {
// Save the focused view if one was set via requestFocus()
mFragment.setFocusedView(mFragment.mView.findFocus());
- // Set the view to INVISIBLE to allow for postponed animations
- mFragment.mView.setVisibility(View.INVISIBLE);
+ // Set the view alpha to 0
+ mFragment.mView.setAlpha(0f);
}
} else {
// Only animate the view if it is visible. This is done after
@@ -726,6 +728,9 @@
}
void destroyFragmentView() {
+ if (FragmentManager.isLoggingEnabled(Log.DEBUG)) {
+ Log.d(TAG, "movefrom CREATE_VIEW: " + mFragment);
+ }
mFragment.performDestroyView();
mDispatcher.dispatchOnFragmentViewDestroyed(mFragment, false);
mFragment.mContainer = null;
diff --git a/fragment/fragment/src/main/java/androidx/fragment/app/SpecialEffectsController.java b/fragment/fragment/src/main/java/androidx/fragment/app/SpecialEffectsController.java
index 818664e..0754334 100644
--- a/fragment/fragment/src/main/java/androidx/fragment/app/SpecialEffectsController.java
+++ b/fragment/fragment/src/main/java/androidx/fragment/app/SpecialEffectsController.java
@@ -196,6 +196,7 @@
void markPostponedState() {
synchronized (mPendingOperations) {
+ updateFinalState(false);
// Default to not postponed
mIsContainerPostponed = false;
for (int index = mPendingOperations.size() - 1; index >= 0; index--) {
@@ -227,6 +228,8 @@
return;
}
synchronized (mPendingOperations) {
+ updateFinalState(true);
+
if (!mPendingOperations.isEmpty()) {
executeOperations(new ArrayList<>(mPendingOperations), mOperationDirectionIsPop);
mPendingOperations.clear();
@@ -237,6 +240,8 @@
void forceCompleteAllOperations() {
synchronized (mPendingOperations) {
+ updateFinalState(true);
+
for (Operation operation : mAwaitingCompletionOperations.values()) {
operation.getCancellationSignal().cancel();
operation.getFinalState().applyState(operation.getFragment().mView);
@@ -249,6 +254,27 @@
}
}
+ private void updateFinalState(boolean updateAlpha) {
+ for (Operation operation: mPendingOperations) {
+ // update the final state of adding operations
+ if (operation.getLifecycleImpact() == Operation.LifecycleImpact.ADDING) {
+ Fragment fragment = operation.getFragment();
+ View view = fragment.requireView();
+ Operation.State finalState = Operation.State.from(view.getVisibility());
+ operation.mergeWith(finalState, Operation.LifecycleImpact.NONE,
+ operation.getCancellationSignal());
+ // Change the view alphas back to their original values before we execute our
+ // transitions.
+ if (updateAlpha) {
+ if (view.getAlpha() == 0f && view.getVisibility() == View.VISIBLE) {
+ view.setVisibility(View.INVISIBLE);
+ }
+ view.setAlpha(fragment.getPostOnViewCreatedAlpha());
+ }
+ }
+ }
+ }
+
/**
* Execute all of the given operations.
* <p>
@@ -305,6 +331,10 @@
*/
@NonNull
static State from(@NonNull View view) {
+ // We should consider views with an alpha of 0 as INVISIBLE.
+ if (view.getAlpha() == 0f && view.getVisibility() == View.VISIBLE) {
+ return INVISIBLE;
+ }
return from(view.getVisibility());
}
diff --git a/hilt/hilt-compiler/build.gradle b/hilt/hilt-compiler/build.gradle
index 9ae3ab0..f452650 100644
--- a/hilt/hilt-compiler/build.gradle
+++ b/hilt/hilt-compiler/build.gradle
@@ -62,15 +62,13 @@
}
// Extract Hilt classes.jar to be used for compile-testing.
-tasks.register("extractHiltJar", Exec).configure {
+tasks.register("extractHiltJar", Copy).configure {
def aarPath = project.configurations.testRuntimeClasspath.find {
it.path.contains("com/google/dagger/hilt-android/") && it.name.endsWith(".aar")
}
- inputs.file(aarPath)
- outputs.dir(new File(project.buildDir, "extractedJar"))
- workingDir(project.buildDir)
- executable("unzip")
- args("-o", "$aarPath", "classes.jar", "-d", "extractedJar")
+ from project.zipTree(aarPath)
+ into new File(project.buildDir, "extractedJar")
+ include "classes.jar"
}
tasks.named("compileKotlin").configure {
diff --git a/hilt/hilt-compiler/src/main/kotlin/androidx/hilt/lifecycle/ViewModelGenerator.kt b/hilt/hilt-compiler/src/main/kotlin/androidx/hilt/lifecycle/ViewModelGenerator.kt
index 42444c6..bd1225f 100644
--- a/hilt/hilt-compiler/src/main/kotlin/androidx/hilt/lifecycle/ViewModelGenerator.kt
+++ b/hilt/hilt-compiler/src/main/kotlin/androidx/hilt/lifecycle/ViewModelGenerator.kt
@@ -31,7 +31,7 @@
import javax.lang.model.element.Modifier
/**
- * Source generator to support Hilt injection of Workers.
+ * Source generator to support Hilt injection of ViewModels.
*
* Should generate:
* ```
diff --git a/inspection/inspection/src/main/java/androidx/inspection/InspectorEnvironment.java b/inspection/inspection/src/main/java/androidx/inspection/InspectorEnvironment.java
index aaba976..04f317a 100644
--- a/inspection/inspection/src/main/java/androidx/inspection/InspectorEnvironment.java
+++ b/inspection/inspection/src/main/java/androidx/inspection/InspectorEnvironment.java
@@ -18,29 +18,11 @@
import androidx.annotation.NonNull;
-import java.util.List;
-
/**
* This interface provides inspector specific utilities, such as
* managed threads and ARTTI features.
*/
-//TODO(b/163335801): remove "extends ArtToolInterface"
-// /temporary implements ArtToolInterface to ease drop to Studio.
-public interface InspectorEnvironment extends ArtToolInterface {
-
- // TODO: will be removed once studio and clients are migrated
- /**
- * This interface will be removed
- * @param <T>
- */
- interface ExitHook<T> extends ArtToolInterface.ExitHook<T> {
- }
-
- /**
- * This interface will be removed
- */
- interface EntryHook extends ArtToolInterface.EntryHook {
- }
+public interface InspectorEnvironment {
/**
* Executors provided by App Inspection Platforms. Clients should use it instead of
@@ -55,44 +37,5 @@
* Interface that provides ART TI capabilities.
*/
@NonNull
- default ArtToolInterface artTI() {
- return this;
- }
-
- // Temporary default implementations (so they can be removed from actual implementation of
- // InspectorEnvironment
- @NonNull
- @Override
- default <T> List<T> findInstances(@NonNull Class<T> clazz) {
- return artTI().findInstances(clazz);
- }
-
- @Override
- default void registerEntryHook(@NonNull Class<?> originClass, @NonNull String originMethod,
- @NonNull ArtToolInterface.EntryHook entryHook) {
- artTI().registerEntryHook(originClass, originMethod, entryHook);
- }
-
- @Override
- default <T> void registerExitHook(@NonNull Class<?> originClass, @NonNull String originMethod,
- @NonNull ArtToolInterface.ExitHook<T> exitHook) {
- artTI().registerExitHook(originClass, originMethod, exitHook);
- }
-
- /**
- * Temporary method for backwards compat. TODO(b/163335801)
- */
- default void registerEntryHook(@NonNull Class<?> originClass, @NonNull String originMethod,
- @NonNull EntryHook entryHook) {
- artTI().registerEntryHook(originClass, originMethod, entryHook);
- }
-
- /**
- * Temporary method for backwards compat. TODO(b/163335801)
- */
- default <T> void registerExitHook(@NonNull Class<?> originClass, @NonNull String originMethod,
- @NonNull ExitHook<T> exitHook) {
- artTI().registerExitHook(originClass, originMethod, exitHook);
- }
-
+ ArtToolInterface artTI();
}
diff --git a/leanback/leanback/api/api_lint.ignore b/leanback/leanback/api/api_lint.ignore
index a1d3a96..d474c6d 100644
--- a/leanback/leanback/api/api_lint.ignore
+++ b/leanback/leanback/api/api_lint.ignore
@@ -67,6 +67,22 @@
Builder methods names should use setFoo() / addFoo() / clearFoo() style: method androidx.leanback.widget.ShadowOverlayHelper.Builder.preferZOrder(boolean)
+CallbackMethodName: androidx.leanback.widget.DiffCallback#areContentsTheSame(Value, Value):
+ Callback method names must follow the on<Something> style: areContentsTheSame
+CallbackMethodName: androidx.leanback.widget.DiffCallback#areItemsTheSame(Value, Value):
+ Callback method names must follow the on<Something> style: areItemsTheSame
+CallbackMethodName: androidx.leanback.widget.DiffCallback#getChangePayload(Value, Value):
+ Callback method names must follow the on<Something> style: getChangePayload
+CallbackMethodName: androidx.leanback.widget.GuidedActionDiffCallback#areContentsTheSame(androidx.leanback.widget.GuidedAction, androidx.leanback.widget.GuidedAction):
+ Callback method names must follow the on<Something> style: areContentsTheSame
+CallbackMethodName: androidx.leanback.widget.GuidedActionDiffCallback#areItemsTheSame(androidx.leanback.widget.GuidedAction, androidx.leanback.widget.GuidedAction):
+ Callback method names must follow the on<Something> style: areItemsTheSame
+CallbackMethodName: androidx.leanback.widget.SearchBar.SearchBarPermissionListener#requestAudioPermission():
+ Listener method names must follow the on<Something> style: requestAudioPermission
+CallbackMethodName: androidx.leanback.widget.SpeechRecognitionCallback#recognizeSpeech():
+ Callback method names must follow the on<Something> style: recognizeSpeech
+
+
CallbackName: androidx.leanback.widget.ObjectAdapter.DataObserver:
Class should be named DataCallback
diff --git a/media/media/api/api_lint.ignore b/media/media/api/api_lint.ignore
index f605218..61e5ee7 100644
--- a/media/media/api/api_lint.ignore
+++ b/media/media/api/api_lint.ignore
@@ -33,6 +33,10 @@
Builder methods names should use setFoo() / addFoo() / clearFoo() style: method android.support.v4.media.MediaMetadataCompat.Builder.putText(String,CharSequence)
+CallbackMethodName: android.support.v4.media.session.MediaControllerCompat.Callback#binderDied():
+ Callback method names must follow the on<Something> style: binderDied
+
+
ContextNameSuffix: androidx.media.MediaBrowserServiceCompat:
Inconsistent class name; should be `<Foo>Service`, was `MediaBrowserServiceCompat`
diff --git a/media/media/src/main/AndroidManifest.xml b/media/media/src/main/AndroidManifest.xml
index 9c20495..5cede58f 100644
--- a/media/media/src/main/AndroidManifest.xml
+++ b/media/media/src/main/AndroidManifest.xml
@@ -13,4 +13,11 @@
See the License for the specific language governing permissions and
limitations under the License.
-->
-<manifest package="androidx.media"/>
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="androidx.media">
+ <queries>
+ <intent>
+ <action android:name="android.media.browse.MediaBrowserService" />
+ </intent>
+ </queries>
+</manifest>
diff --git a/media/version-compat-tests/current/client/src/androidTest/AndroidManifest.xml b/media/version-compat-tests/current/client/src/androidTest/AndroidManifest.xml
index afe1865..ded115a 100644
--- a/media/version-compat-tests/current/client/src/androidTest/AndroidManifest.xml
+++ b/media/version-compat-tests/current/client/src/androidTest/AndroidManifest.xml
@@ -16,6 +16,10 @@
-->
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="android.support.mediacompat.client.test">
+ <queries>
+ <package android:name="android.support.mediacompat.service.test" />
+ </queries>
+
<application android:supportsRtl="true">
<receiver android:name="android.support.mediacompat.client.ClientBroadcastReceiver">
<intent-filter>
diff --git a/media/version-compat-tests/current/service/src/androidTest/AndroidManifest.xml b/media/version-compat-tests/current/service/src/androidTest/AndroidManifest.xml
index 13c22ae..0dd080e 100644
--- a/media/version-compat-tests/current/service/src/androidTest/AndroidManifest.xml
+++ b/media/version-compat-tests/current/service/src/androidTest/AndroidManifest.xml
@@ -16,6 +16,10 @@
-->
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="android.support.mediacompat.service.test">
+ <queries>
+ <package android:name="android.support.mediacompat.client.test" />
+ </queries>
+
<application>
<receiver android:name="android.support.mediacompat.service.ServiceBroadcastReceiver">
<intent-filter>
diff --git a/media/version-compat-tests/previous/client/src/androidTest/AndroidManifest.xml b/media/version-compat-tests/previous/client/src/androidTest/AndroidManifest.xml
index afe1865..ded115a 100644
--- a/media/version-compat-tests/previous/client/src/androidTest/AndroidManifest.xml
+++ b/media/version-compat-tests/previous/client/src/androidTest/AndroidManifest.xml
@@ -16,6 +16,10 @@
-->
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="android.support.mediacompat.client.test">
+ <queries>
+ <package android:name="android.support.mediacompat.service.test" />
+ </queries>
+
<application android:supportsRtl="true">
<receiver android:name="android.support.mediacompat.client.ClientBroadcastReceiver">
<intent-filter>
diff --git a/media/version-compat-tests/previous/service/src/androidTest/AndroidManifest.xml b/media/version-compat-tests/previous/service/src/androidTest/AndroidManifest.xml
index b47eecf..80b0f48 100644
--- a/media/version-compat-tests/previous/service/src/androidTest/AndroidManifest.xml
+++ b/media/version-compat-tests/previous/service/src/androidTest/AndroidManifest.xml
@@ -16,6 +16,10 @@
-->
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="android.support.mediacompat.service.test">
+ <queries>
+ <package android:name="android.support.mediacompat.client.test" />
+ </queries>
+
<application>
<receiver android:name="android.support.mediacompat.service.ServiceBroadcastReceiver">
<intent-filter>
diff --git a/media2/common/api/api_lint.ignore b/media2/common/api/api_lint.ignore
index 8464891..8d8d8aa 100644
--- a/media2/common/api/api_lint.ignore
+++ b/media2/common/api/api_lint.ignore
@@ -13,6 +13,12 @@
Builder methods names should use setFoo() / addFoo() / clearFoo() style: method androidx.media2.common.MediaMetadata.Builder.putText(String,CharSequence)
+CallbackMethodName: androidx.media2.common.DataSourceCallback#getSize():
+ Callback method names must follow the on<Something> style: getSize
+CallbackMethodName: androidx.media2.common.DataSourceCallback#readAt(long, byte[], int, int):
+ Callback method names must follow the on<Something> style: readAt
+
+
ExecutorRegistration: androidx.media2.common.CallbackMediaItem.Builder#Builder(androidx.media2.common.DataSourceCallback):
Registration methods should have overload that accepts delivery Executor: `Builder`
diff --git a/media2/session/src/main/AndroidManifest.xml b/media2/session/src/main/AndroidManifest.xml
index 0ef9ddd..53a302b 100644
--- a/media2/session/src/main/AndroidManifest.xml
+++ b/media2/session/src/main/AndroidManifest.xml
@@ -13,4 +13,17 @@
See the License for the specific language governing permissions and
limitations under the License.
-->
-<manifest package="androidx.media2.session"/>
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="androidx.media2.session">
+ <queries>
+ <intent>
+ <action android:name="android.media.browse.MediaBrowserService" />
+ </intent>
+ <intent>
+ <action android:name="androidx.media2.session.MediaSessionService" />
+ </intent>
+ <intent>
+ <action android:name="androidx.media2.session.MediaLibraryService" />
+ </intent>
+ </queries>
+</manifest>
diff --git a/media2/session/src/main/java/androidx/media2/session/MediaControllerImplBase.java b/media2/session/src/main/java/androidx/media2/session/MediaControllerImplBase.java
index 07e109f..a1a90918 100644
--- a/media2/session/src/main/java/androidx/media2/session/MediaControllerImplBase.java
+++ b/media2/session/src/main/java/androidx/media2/session/MediaControllerImplBase.java
@@ -522,7 +522,7 @@
@Override
public List<MediaItem> getPlaylist() {
synchronized (mLock) {
- return new ArrayList<>(mPlaylist);
+ return mPlaylist == null ? null : new ArrayList<>(mPlaylist);
}
}
diff --git a/media2/session/version-compat-tests/current/client/src/androidTest/AndroidManifest.xml b/media2/session/version-compat-tests/current/client/src/androidTest/AndroidManifest.xml
index ca16534..9b01c01 100644
--- a/media2/session/version-compat-tests/current/client/src/androidTest/AndroidManifest.xml
+++ b/media2/session/version-compat-tests/current/client/src/androidTest/AndroidManifest.xml
@@ -16,6 +16,10 @@
-->
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="androidx.media2.test.client.test">
+ <queries>
+ <package android:name="androidx.media2.test.service.test" />
+ </queries>
+
<application android:supportsRtl="true">
<activity android:name="androidx.media2.test.client.SurfaceActivity" />
diff --git a/media2/session/version-compat-tests/current/client/src/androidTest/java/androidx/media2/test/client/tests/MediaControllerCallbackTest.java b/media2/session/version-compat-tests/current/client/src/androidTest/java/androidx/media2/test/client/tests/MediaControllerCallbackTest.java
index 98cd3d3..8b228ee 100644
--- a/media2/session/version-compat-tests/current/client/src/androidTest/java/androidx/media2/test/client/tests/MediaControllerCallbackTest.java
+++ b/media2/session/version-compat-tests/current/client/src/androidTest/java/androidx/media2/test/client/tests/MediaControllerCallbackTest.java
@@ -613,6 +613,7 @@
mRemoteSession2.getMockPlayer().setPlaylist(null);
mRemoteSession2.getMockPlayer().notifyPlaylistChanged();
assertTrue(latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
+ assertNull(controller.getPlaylist());
}
@Test
diff --git a/media2/session/version-compat-tests/current/service/src/androidTest/AndroidManifest.xml b/media2/session/version-compat-tests/current/service/src/androidTest/AndroidManifest.xml
index 3ca5622..d033d90 100644
--- a/media2/session/version-compat-tests/current/service/src/androidTest/AndroidManifest.xml
+++ b/media2/session/version-compat-tests/current/service/src/androidTest/AndroidManifest.xml
@@ -18,6 +18,10 @@
package="androidx.media2.test.service.test">
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
+ <queries>
+ <package android:name="androidx.media2.test.client.test" />
+ </queries>
+
<application>
<receiver android:name="androidx.media.session.MediaButtonReceiver" >
<intent-filter>
diff --git a/media2/session/version-compat-tests/previous/client/src/androidTest/AndroidManifest.xml b/media2/session/version-compat-tests/previous/client/src/androidTest/AndroidManifest.xml
index ca16534..9b01c01 100644
--- a/media2/session/version-compat-tests/previous/client/src/androidTest/AndroidManifest.xml
+++ b/media2/session/version-compat-tests/previous/client/src/androidTest/AndroidManifest.xml
@@ -16,6 +16,10 @@
-->
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="androidx.media2.test.client.test">
+ <queries>
+ <package android:name="androidx.media2.test.service.test" />
+ </queries>
+
<application android:supportsRtl="true">
<activity android:name="androidx.media2.test.client.SurfaceActivity" />
diff --git a/media2/session/version-compat-tests/previous/service/src/androidTest/AndroidManifest.xml b/media2/session/version-compat-tests/previous/service/src/androidTest/AndroidManifest.xml
index 3ca5622..d033d90 100644
--- a/media2/session/version-compat-tests/previous/service/src/androidTest/AndroidManifest.xml
+++ b/media2/session/version-compat-tests/previous/service/src/androidTest/AndroidManifest.xml
@@ -18,6 +18,10 @@
package="androidx.media2.test.service.test">
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
+ <queries>
+ <package android:name="androidx.media2.test.client.test" />
+ </queries>
+
<application>
<receiver android:name="androidx.media.session.MediaButtonReceiver" >
<intent-filter>
diff --git a/mediarouter/mediarouter/api/1.2.0-beta01.txt b/mediarouter/mediarouter/api/1.2.0-beta01.txt
new file mode 100644
index 0000000..12b4c5c
--- /dev/null
+++ b/mediarouter/mediarouter/api/1.2.0-beta01.txt
@@ -0,0 +1,550 @@
+// Signature format: 3.0
+package androidx.mediarouter.app {
+
+ public class MediaRouteActionProvider extends androidx.core.view.ActionProvider {
+ ctor public MediaRouteActionProvider(android.content.Context!);
+ method @Deprecated public void enableDynamicGroup();
+ method public androidx.mediarouter.app.MediaRouteDialogFactory getDialogFactory();
+ method public androidx.mediarouter.app.MediaRouteButton? getMediaRouteButton();
+ method public androidx.mediarouter.media.MediaRouteSelector getRouteSelector();
+ method public android.view.View! onCreateActionView();
+ method public androidx.mediarouter.app.MediaRouteButton! onCreateMediaRouteButton();
+ method public void setAlwaysVisible(boolean);
+ method public void setDialogFactory(androidx.mediarouter.app.MediaRouteDialogFactory);
+ method public void setRouteSelector(androidx.mediarouter.media.MediaRouteSelector);
+ }
+
+ public class MediaRouteButton extends android.view.View {
+ ctor public MediaRouteButton(android.content.Context!);
+ ctor public MediaRouteButton(android.content.Context!, android.util.AttributeSet!);
+ ctor public MediaRouteButton(android.content.Context!, android.util.AttributeSet!, int);
+ method @Deprecated public void enableDynamicGroup();
+ method public androidx.mediarouter.app.MediaRouteDialogFactory getDialogFactory();
+ method public androidx.mediarouter.media.MediaRouteSelector getRouteSelector();
+ method public void onAttachedToWindow();
+ method public void onDetachedFromWindow();
+ method public void setAlwaysVisible(boolean);
+ method public void setDialogFactory(androidx.mediarouter.app.MediaRouteDialogFactory);
+ method public void setRemoteIndicatorDrawable(android.graphics.drawable.Drawable!);
+ method public void setRouteSelector(androidx.mediarouter.media.MediaRouteSelector!);
+ method public boolean showDialog();
+ }
+
+ public class MediaRouteChooserDialog extends androidx.appcompat.app.AppCompatDialog {
+ ctor public MediaRouteChooserDialog(android.content.Context!);
+ ctor public MediaRouteChooserDialog(android.content.Context!, int);
+ method public androidx.mediarouter.media.MediaRouteSelector getRouteSelector();
+ method public boolean onFilterRoute(androidx.mediarouter.media.MediaRouter.RouteInfo);
+ method public void onFilterRoutes(java.util.List<androidx.mediarouter.media.MediaRouter.RouteInfo!>);
+ method public void refreshRoutes();
+ method public void setRouteSelector(androidx.mediarouter.media.MediaRouteSelector);
+ }
+
+ public class MediaRouteChooserDialogFragment extends androidx.fragment.app.DialogFragment {
+ ctor public MediaRouteChooserDialogFragment();
+ method public androidx.mediarouter.media.MediaRouteSelector! getRouteSelector();
+ method public androidx.mediarouter.app.MediaRouteChooserDialog! onCreateChooserDialog(android.content.Context!, android.os.Bundle!);
+ method public void setRouteSelector(androidx.mediarouter.media.MediaRouteSelector!);
+ }
+
+ public class MediaRouteControllerDialog extends androidx.appcompat.app.AlertDialog {
+ ctor public MediaRouteControllerDialog(android.content.Context!);
+ ctor public MediaRouteControllerDialog(android.content.Context!, int);
+ method public android.view.View! getMediaControlView();
+ method public android.support.v4.media.session.MediaSessionCompat.Token! getMediaSession();
+ method public androidx.mediarouter.media.MediaRouter.RouteInfo! getRoute();
+ method public boolean isVolumeControlEnabled();
+ method public android.view.View! onCreateMediaControlView(android.os.Bundle!);
+ method public void setVolumeControlEnabled(boolean);
+ }
+
+ public class MediaRouteControllerDialogFragment extends androidx.fragment.app.DialogFragment {
+ ctor public MediaRouteControllerDialogFragment();
+ method public androidx.mediarouter.app.MediaRouteControllerDialog! onCreateControllerDialog(android.content.Context!, android.os.Bundle!);
+ }
+
+ public class MediaRouteDialogFactory {
+ ctor public MediaRouteDialogFactory();
+ method public static androidx.mediarouter.app.MediaRouteDialogFactory getDefault();
+ method public androidx.mediarouter.app.MediaRouteChooserDialogFragment onCreateChooserDialogFragment();
+ method public androidx.mediarouter.app.MediaRouteControllerDialogFragment onCreateControllerDialogFragment();
+ }
+
+ public class MediaRouteDiscoveryFragment extends androidx.fragment.app.Fragment {
+ ctor public MediaRouteDiscoveryFragment();
+ method public androidx.mediarouter.media.MediaRouter! getMediaRouter();
+ method public androidx.mediarouter.media.MediaRouteSelector! getRouteSelector();
+ method public androidx.mediarouter.media.MediaRouter.Callback! onCreateCallback();
+ method public int onPrepareCallbackFlags();
+ method public void setRouteSelector(androidx.mediarouter.media.MediaRouteSelector!);
+ }
+
+}
+
+package androidx.mediarouter.media {
+
+ public final class MediaControlIntent {
+ field public static final String ACTION_END_SESSION = "android.media.intent.action.END_SESSION";
+ field public static final String ACTION_ENQUEUE = "android.media.intent.action.ENQUEUE";
+ field public static final String ACTION_GET_SESSION_STATUS = "android.media.intent.action.GET_SESSION_STATUS";
+ field public static final String ACTION_GET_STATUS = "android.media.intent.action.GET_STATUS";
+ field public static final String ACTION_PAUSE = "android.media.intent.action.PAUSE";
+ field public static final String ACTION_PLAY = "android.media.intent.action.PLAY";
+ field public static final String ACTION_REMOVE = "android.media.intent.action.REMOVE";
+ field public static final String ACTION_RESUME = "android.media.intent.action.RESUME";
+ field public static final String ACTION_SEEK = "android.media.intent.action.SEEK";
+ field public static final String ACTION_SEND_MESSAGE = "android.media.intent.action.SEND_MESSAGE";
+ field public static final String ACTION_START_SESSION = "android.media.intent.action.START_SESSION";
+ field public static final String ACTION_STOP = "android.media.intent.action.STOP";
+ field public static final String CATEGORY_LIVE_AUDIO = "android.media.intent.category.LIVE_AUDIO";
+ field public static final String CATEGORY_LIVE_VIDEO = "android.media.intent.category.LIVE_VIDEO";
+ field public static final String CATEGORY_REMOTE_PLAYBACK = "android.media.intent.category.REMOTE_PLAYBACK";
+ field public static final int ERROR_INVALID_ITEM_ID = 3; // 0x3
+ field public static final int ERROR_INVALID_SESSION_ID = 2; // 0x2
+ field public static final int ERROR_UNKNOWN = 0; // 0x0
+ field public static final int ERROR_UNSUPPORTED_OPERATION = 1; // 0x1
+ field public static final String EXTRA_ERROR_CODE = "android.media.intent.extra.ERROR_CODE";
+ field public static final String EXTRA_ITEM_CONTENT_POSITION = "android.media.intent.extra.ITEM_POSITION";
+ field public static final String EXTRA_ITEM_HTTP_HEADERS = "android.media.intent.extra.HTTP_HEADERS";
+ field public static final String EXTRA_ITEM_ID = "android.media.intent.extra.ITEM_ID";
+ field public static final String EXTRA_ITEM_METADATA = "android.media.intent.extra.ITEM_METADATA";
+ field public static final String EXTRA_ITEM_STATUS = "android.media.intent.extra.ITEM_STATUS";
+ field public static final String EXTRA_ITEM_STATUS_UPDATE_RECEIVER = "android.media.intent.extra.ITEM_STATUS_UPDATE_RECEIVER";
+ field public static final String EXTRA_MESSAGE = "android.media.intent.extra.MESSAGE";
+ field public static final String EXTRA_MESSAGE_RECEIVER = "android.media.intent.extra.MESSAGE_RECEIVER";
+ field public static final String EXTRA_SESSION_ID = "android.media.intent.extra.SESSION_ID";
+ field public static final String EXTRA_SESSION_STATUS = "android.media.intent.extra.SESSION_STATUS";
+ field public static final String EXTRA_SESSION_STATUS_UPDATE_RECEIVER = "android.media.intent.extra.SESSION_STATUS_UPDATE_RECEIVER";
+ }
+
+ public final class MediaItemMetadata {
+ field public static final String KEY_ALBUM_ARTIST = "android.media.metadata.ALBUM_ARTIST";
+ field public static final String KEY_ALBUM_TITLE = "android.media.metadata.ALBUM_TITLE";
+ field public static final String KEY_ARTIST = "android.media.metadata.ARTIST";
+ field public static final String KEY_ARTWORK_URI = "android.media.metadata.ARTWORK_URI";
+ field public static final String KEY_AUTHOR = "android.media.metadata.AUTHOR";
+ field public static final String KEY_COMPOSER = "android.media.metadata.COMPOSER";
+ field public static final String KEY_DISC_NUMBER = "android.media.metadata.DISC_NUMBER";
+ field public static final String KEY_DURATION = "android.media.metadata.DURATION";
+ field public static final String KEY_TITLE = "android.media.metadata.TITLE";
+ field public static final String KEY_TRACK_NUMBER = "android.media.metadata.TRACK_NUMBER";
+ field public static final String KEY_YEAR = "android.media.metadata.YEAR";
+ }
+
+ public final class MediaItemStatus {
+ method public android.os.Bundle! asBundle();
+ method public static androidx.mediarouter.media.MediaItemStatus! fromBundle(android.os.Bundle!);
+ method public long getContentDuration();
+ method public long getContentPosition();
+ method public android.os.Bundle! getExtras();
+ method public int getPlaybackState();
+ method public long getTimestamp();
+ field public static final String EXTRA_HTTP_RESPONSE_HEADERS = "android.media.status.extra.HTTP_RESPONSE_HEADERS";
+ field public static final String EXTRA_HTTP_STATUS_CODE = "android.media.status.extra.HTTP_STATUS_CODE";
+ field public static final int PLAYBACK_STATE_BUFFERING = 3; // 0x3
+ field public static final int PLAYBACK_STATE_CANCELED = 5; // 0x5
+ field public static final int PLAYBACK_STATE_ERROR = 7; // 0x7
+ field public static final int PLAYBACK_STATE_FINISHED = 4; // 0x4
+ field public static final int PLAYBACK_STATE_INVALIDATED = 6; // 0x6
+ field public static final int PLAYBACK_STATE_PAUSED = 2; // 0x2
+ field public static final int PLAYBACK_STATE_PENDING = 0; // 0x0
+ field public static final int PLAYBACK_STATE_PLAYING = 1; // 0x1
+ }
+
+ public static final class MediaItemStatus.Builder {
+ ctor public MediaItemStatus.Builder(int);
+ ctor public MediaItemStatus.Builder(androidx.mediarouter.media.MediaItemStatus!);
+ method public androidx.mediarouter.media.MediaItemStatus! build();
+ method public androidx.mediarouter.media.MediaItemStatus.Builder! setContentDuration(long);
+ method public androidx.mediarouter.media.MediaItemStatus.Builder! setContentPosition(long);
+ method public androidx.mediarouter.media.MediaItemStatus.Builder! setExtras(android.os.Bundle!);
+ method public androidx.mediarouter.media.MediaItemStatus.Builder! setPlaybackState(int);
+ method public androidx.mediarouter.media.MediaItemStatus.Builder! setTimestamp(long);
+ }
+
+ public final class MediaRouteDescriptor {
+ method public android.os.Bundle! asBundle();
+ method public boolean canDisconnectAndKeepPlaying();
+ method public static androidx.mediarouter.media.MediaRouteDescriptor! fromBundle(android.os.Bundle!);
+ method public int getConnectionState();
+ method public java.util.List<android.content.IntentFilter!>! getControlFilters();
+ method public String! getDescription();
+ method public int getDeviceType();
+ method public android.os.Bundle! getExtras();
+ method public android.net.Uri! getIconUri();
+ method public String! getId();
+ method public String! getName();
+ method public int getPlaybackStream();
+ method public int getPlaybackType();
+ method public int getPresentationDisplayId();
+ method public android.content.IntentSender! getSettingsActivity();
+ method public int getVolume();
+ method public int getVolumeHandling();
+ method public int getVolumeMax();
+ method @Deprecated public boolean isConnecting();
+ method public boolean isDynamicGroupRoute();
+ method public boolean isEnabled();
+ method public boolean isValid();
+ }
+
+ public static final class MediaRouteDescriptor.Builder {
+ ctor public MediaRouteDescriptor.Builder(String!, String!);
+ ctor public MediaRouteDescriptor.Builder(androidx.mediarouter.media.MediaRouteDescriptor!);
+ method public androidx.mediarouter.media.MediaRouteDescriptor.Builder! addControlFilter(android.content.IntentFilter!);
+ method public androidx.mediarouter.media.MediaRouteDescriptor.Builder! addControlFilters(java.util.Collection<android.content.IntentFilter!>!);
+ method public androidx.mediarouter.media.MediaRouteDescriptor! build();
+ method public androidx.mediarouter.media.MediaRouteDescriptor.Builder! setCanDisconnect(boolean);
+ method @Deprecated public androidx.mediarouter.media.MediaRouteDescriptor.Builder! setConnecting(boolean);
+ method public androidx.mediarouter.media.MediaRouteDescriptor.Builder! setConnectionState(int);
+ method public androidx.mediarouter.media.MediaRouteDescriptor.Builder! setDescription(String!);
+ method public androidx.mediarouter.media.MediaRouteDescriptor.Builder! setDeviceType(int);
+ method public androidx.mediarouter.media.MediaRouteDescriptor.Builder! setEnabled(boolean);
+ method public androidx.mediarouter.media.MediaRouteDescriptor.Builder! setExtras(android.os.Bundle!);
+ method public androidx.mediarouter.media.MediaRouteDescriptor.Builder! setIconUri(android.net.Uri!);
+ method public androidx.mediarouter.media.MediaRouteDescriptor.Builder! setId(String!);
+ method public androidx.mediarouter.media.MediaRouteDescriptor.Builder! setIsDynamicGroupRoute(boolean);
+ method public androidx.mediarouter.media.MediaRouteDescriptor.Builder! setName(String!);
+ method public androidx.mediarouter.media.MediaRouteDescriptor.Builder! setPlaybackStream(int);
+ method public androidx.mediarouter.media.MediaRouteDescriptor.Builder! setPlaybackType(int);
+ method public androidx.mediarouter.media.MediaRouteDescriptor.Builder! setPresentationDisplayId(int);
+ method public androidx.mediarouter.media.MediaRouteDescriptor.Builder! setSettingsActivity(android.content.IntentSender!);
+ method public androidx.mediarouter.media.MediaRouteDescriptor.Builder! setVolume(int);
+ method public androidx.mediarouter.media.MediaRouteDescriptor.Builder! setVolumeHandling(int);
+ method public androidx.mediarouter.media.MediaRouteDescriptor.Builder! setVolumeMax(int);
+ }
+
+ public final class MediaRouteDiscoveryRequest {
+ ctor public MediaRouteDiscoveryRequest(androidx.mediarouter.media.MediaRouteSelector!, boolean);
+ method public android.os.Bundle! asBundle();
+ method public static androidx.mediarouter.media.MediaRouteDiscoveryRequest! fromBundle(android.os.Bundle!);
+ method public androidx.mediarouter.media.MediaRouteSelector! getSelector();
+ method public boolean isActiveScan();
+ method public boolean isValid();
+ }
+
+ public abstract class MediaRouteProvider {
+ ctor public MediaRouteProvider(android.content.Context);
+ method public final android.content.Context! getContext();
+ method public final androidx.mediarouter.media.MediaRouteProviderDescriptor? getDescriptor();
+ method public final androidx.mediarouter.media.MediaRouteDiscoveryRequest? getDiscoveryRequest();
+ method public final android.os.Handler! getHandler();
+ method public final androidx.mediarouter.media.MediaRouteProvider.ProviderMetadata! getMetadata();
+ method public androidx.mediarouter.media.MediaRouteProvider.DynamicGroupRouteController? onCreateDynamicGroupRouteController(String);
+ method public androidx.mediarouter.media.MediaRouteProvider.RouteController? onCreateRouteController(String);
+ method public void onDiscoveryRequestChanged(androidx.mediarouter.media.MediaRouteDiscoveryRequest?);
+ method public final void setCallback(androidx.mediarouter.media.MediaRouteProvider.Callback?);
+ method public final void setDescriptor(androidx.mediarouter.media.MediaRouteProviderDescriptor?);
+ method public final void setDiscoveryRequest(androidx.mediarouter.media.MediaRouteDiscoveryRequest!);
+ }
+
+ public abstract static class MediaRouteProvider.Callback {
+ ctor public MediaRouteProvider.Callback();
+ method public void onDescriptorChanged(androidx.mediarouter.media.MediaRouteProvider, androidx.mediarouter.media.MediaRouteProviderDescriptor?);
+ }
+
+ public abstract static class MediaRouteProvider.DynamicGroupRouteController extends androidx.mediarouter.media.MediaRouteProvider.RouteController {
+ ctor public MediaRouteProvider.DynamicGroupRouteController();
+ method public String? getGroupableSelectionTitle();
+ method public String? getTransferableSectionTitle();
+ method @Deprecated public final void notifyDynamicRoutesChanged(java.util.Collection<androidx.mediarouter.media.MediaRouteProvider.DynamicGroupRouteController.DynamicRouteDescriptor!>!);
+ method public final void notifyDynamicRoutesChanged(androidx.mediarouter.media.MediaRouteDescriptor, java.util.Collection<androidx.mediarouter.media.MediaRouteProvider.DynamicGroupRouteController.DynamicRouteDescriptor!>);
+ method public abstract void onAddMemberRoute(String);
+ method public abstract void onRemoveMemberRoute(String!);
+ method public abstract void onUpdateMemberRoutes(java.util.List<java.lang.String!>?);
+ }
+
+ public static final class MediaRouteProvider.DynamicGroupRouteController.DynamicRouteDescriptor {
+ method public androidx.mediarouter.media.MediaRouteDescriptor getRouteDescriptor();
+ method public int getSelectionState();
+ method public boolean isGroupable();
+ method public boolean isTransferable();
+ method public boolean isUnselectable();
+ field public static final int SELECTED = 3; // 0x3
+ field public static final int SELECTING = 2; // 0x2
+ field public static final int UNSELECTED = 1; // 0x1
+ field public static final int UNSELECTING = 0; // 0x0
+ }
+
+ public static final class MediaRouteProvider.DynamicGroupRouteController.DynamicRouteDescriptor.Builder {
+ ctor public MediaRouteProvider.DynamicGroupRouteController.DynamicRouteDescriptor.Builder(androidx.mediarouter.media.MediaRouteDescriptor!);
+ ctor public MediaRouteProvider.DynamicGroupRouteController.DynamicRouteDescriptor.Builder(androidx.mediarouter.media.MediaRouteProvider.DynamicGroupRouteController.DynamicRouteDescriptor!);
+ method public androidx.mediarouter.media.MediaRouteProvider.DynamicGroupRouteController.DynamicRouteDescriptor! build();
+ method public androidx.mediarouter.media.MediaRouteProvider.DynamicGroupRouteController.DynamicRouteDescriptor.Builder! setIsGroupable(boolean);
+ method public androidx.mediarouter.media.MediaRouteProvider.DynamicGroupRouteController.DynamicRouteDescriptor.Builder! setIsTransferable(boolean);
+ method public androidx.mediarouter.media.MediaRouteProvider.DynamicGroupRouteController.DynamicRouteDescriptor.Builder! setIsUnselectable(boolean);
+ method public androidx.mediarouter.media.MediaRouteProvider.DynamicGroupRouteController.DynamicRouteDescriptor.Builder! setSelectionState(int);
+ }
+
+ public static final class MediaRouteProvider.ProviderMetadata {
+ method public android.content.ComponentName! getComponentName();
+ method public String! getPackageName();
+ }
+
+ public abstract static class MediaRouteProvider.RouteController {
+ ctor public MediaRouteProvider.RouteController();
+ method public boolean onControlRequest(android.content.Intent!, androidx.mediarouter.media.MediaRouter.ControlRequestCallback?);
+ method public void onRelease();
+ method public void onSelect();
+ method public void onSetVolume(int);
+ method @Deprecated public void onUnselect();
+ method public void onUnselect(int);
+ method public void onUpdateVolume(int);
+ }
+
+ public final class MediaRouteProviderDescriptor {
+ method public android.os.Bundle! asBundle();
+ method public static androidx.mediarouter.media.MediaRouteProviderDescriptor! fromBundle(android.os.Bundle!);
+ method public java.util.List<androidx.mediarouter.media.MediaRouteDescriptor!> getRoutes();
+ method public boolean isValid();
+ method public boolean supportsDynamicGroupRoute();
+ }
+
+ public static final class MediaRouteProviderDescriptor.Builder {
+ ctor public MediaRouteProviderDescriptor.Builder();
+ ctor public MediaRouteProviderDescriptor.Builder(androidx.mediarouter.media.MediaRouteProviderDescriptor!);
+ method public androidx.mediarouter.media.MediaRouteProviderDescriptor.Builder! addRoute(androidx.mediarouter.media.MediaRouteDescriptor!);
+ method public androidx.mediarouter.media.MediaRouteProviderDescriptor.Builder! addRoutes(java.util.Collection<androidx.mediarouter.media.MediaRouteDescriptor!>!);
+ method public androidx.mediarouter.media.MediaRouteProviderDescriptor! build();
+ method public androidx.mediarouter.media.MediaRouteProviderDescriptor.Builder! setSupportsDynamicGroupRoute(boolean);
+ }
+
+ public abstract class MediaRouteProviderService extends android.app.Service {
+ ctor public MediaRouteProviderService();
+ method public androidx.mediarouter.media.MediaRouteProvider! getMediaRouteProvider();
+ method public android.os.IBinder! onBind(android.content.Intent!);
+ method public abstract androidx.mediarouter.media.MediaRouteProvider! onCreateMediaRouteProvider();
+ field public static final String SERVICE_INTERFACE = "android.media.MediaRouteProviderService";
+ }
+
+ public final class MediaRouteSelector {
+ method public android.os.Bundle! asBundle();
+ method public boolean contains(androidx.mediarouter.media.MediaRouteSelector!);
+ method public static androidx.mediarouter.media.MediaRouteSelector! fromBundle(android.os.Bundle?);
+ method public java.util.List<java.lang.String!>! getControlCategories();
+ method public boolean hasControlCategory(String!);
+ method public boolean isEmpty();
+ method public boolean isValid();
+ method public boolean matchesControlFilters(java.util.List<android.content.IntentFilter!>!);
+ field public static final androidx.mediarouter.media.MediaRouteSelector! EMPTY;
+ }
+
+ public static final class MediaRouteSelector.Builder {
+ ctor public MediaRouteSelector.Builder();
+ ctor public MediaRouteSelector.Builder(androidx.mediarouter.media.MediaRouteSelector);
+ method public androidx.mediarouter.media.MediaRouteSelector.Builder addControlCategories(java.util.Collection<java.lang.String!>);
+ method public androidx.mediarouter.media.MediaRouteSelector.Builder addControlCategory(String);
+ method public androidx.mediarouter.media.MediaRouteSelector.Builder addSelector(androidx.mediarouter.media.MediaRouteSelector);
+ method public androidx.mediarouter.media.MediaRouteSelector build();
+ }
+
+ public final class MediaRouter {
+ method public void addCallback(androidx.mediarouter.media.MediaRouteSelector!, androidx.mediarouter.media.MediaRouter.Callback!);
+ method public void addCallback(androidx.mediarouter.media.MediaRouteSelector, androidx.mediarouter.media.MediaRouter.Callback, int);
+ method public void addProvider(androidx.mediarouter.media.MediaRouteProvider);
+ method public void addRemoteControlClient(Object);
+ method public androidx.mediarouter.media.MediaRouter.RouteInfo! getBluetoothRoute();
+ method public androidx.mediarouter.media.MediaRouter.RouteInfo getDefaultRoute();
+ method public static androidx.mediarouter.media.MediaRouter! getInstance(android.content.Context);
+ method public android.support.v4.media.session.MediaSessionCompat.Token! getMediaSessionToken();
+ method public java.util.List<androidx.mediarouter.media.MediaRouter.ProviderInfo!>! getProviders();
+ method public androidx.mediarouter.media.MediaRouterParams? getRouterParams();
+ method public java.util.List<androidx.mediarouter.media.MediaRouter.RouteInfo!>! getRoutes();
+ method public androidx.mediarouter.media.MediaRouter.RouteInfo getSelectedRoute();
+ method public boolean isRouteAvailable(androidx.mediarouter.media.MediaRouteSelector, int);
+ method public void removeCallback(androidx.mediarouter.media.MediaRouter.Callback);
+ method public void removeProvider(androidx.mediarouter.media.MediaRouteProvider);
+ method public void removeRemoteControlClient(Object);
+ method public void selectRoute(androidx.mediarouter.media.MediaRouter.RouteInfo);
+ method public void setMediaSession(Object!);
+ method public void setMediaSessionCompat(android.support.v4.media.session.MediaSessionCompat!);
+ method @MainThread public void setOnPrepareTransferListener(androidx.mediarouter.media.MediaRouter.OnPrepareTransferListener?);
+ method public void setRouterParams(androidx.mediarouter.media.MediaRouterParams?);
+ method public void unselect(int);
+ method public androidx.mediarouter.media.MediaRouter.RouteInfo updateSelectedRoute(androidx.mediarouter.media.MediaRouteSelector);
+ field public static final int AVAILABILITY_FLAG_IGNORE_DEFAULT_ROUTE = 1; // 0x1
+ field public static final int AVAILABILITY_FLAG_REQUIRE_MATCH = 2; // 0x2
+ field public static final int CALLBACK_FLAG_FORCE_DISCOVERY = 8; // 0x8
+ field public static final int CALLBACK_FLAG_PERFORM_ACTIVE_SCAN = 1; // 0x1
+ field public static final int CALLBACK_FLAG_REQUEST_DISCOVERY = 4; // 0x4
+ field public static final int CALLBACK_FLAG_UNFILTERED_EVENTS = 2; // 0x2
+ field public static final int UNSELECT_REASON_DISCONNECTED = 1; // 0x1
+ field public static final int UNSELECT_REASON_ROUTE_CHANGED = 3; // 0x3
+ field public static final int UNSELECT_REASON_STOPPED = 2; // 0x2
+ field public static final int UNSELECT_REASON_UNKNOWN = 0; // 0x0
+ }
+
+ public abstract static class MediaRouter.Callback {
+ ctor public MediaRouter.Callback();
+ method public void onProviderAdded(androidx.mediarouter.media.MediaRouter!, androidx.mediarouter.media.MediaRouter.ProviderInfo!);
+ method public void onProviderChanged(androidx.mediarouter.media.MediaRouter!, androidx.mediarouter.media.MediaRouter.ProviderInfo!);
+ method public void onProviderRemoved(androidx.mediarouter.media.MediaRouter!, androidx.mediarouter.media.MediaRouter.ProviderInfo!);
+ method public void onRouteAdded(androidx.mediarouter.media.MediaRouter!, androidx.mediarouter.media.MediaRouter.RouteInfo!);
+ method public void onRouteChanged(androidx.mediarouter.media.MediaRouter!, androidx.mediarouter.media.MediaRouter.RouteInfo!);
+ method public void onRoutePresentationDisplayChanged(androidx.mediarouter.media.MediaRouter!, androidx.mediarouter.media.MediaRouter.RouteInfo!);
+ method public void onRouteRemoved(androidx.mediarouter.media.MediaRouter!, androidx.mediarouter.media.MediaRouter.RouteInfo!);
+ method @Deprecated public void onRouteSelected(androidx.mediarouter.media.MediaRouter!, androidx.mediarouter.media.MediaRouter.RouteInfo!);
+ method public void onRouteSelected(androidx.mediarouter.media.MediaRouter, androidx.mediarouter.media.MediaRouter.RouteInfo, int);
+ method public void onRouteSelected(androidx.mediarouter.media.MediaRouter, androidx.mediarouter.media.MediaRouter.RouteInfo, int, androidx.mediarouter.media.MediaRouter.RouteInfo);
+ method @Deprecated public void onRouteUnselected(androidx.mediarouter.media.MediaRouter!, androidx.mediarouter.media.MediaRouter.RouteInfo!);
+ method public void onRouteUnselected(androidx.mediarouter.media.MediaRouter!, androidx.mediarouter.media.MediaRouter.RouteInfo!, int);
+ method public void onRouteVolumeChanged(androidx.mediarouter.media.MediaRouter!, androidx.mediarouter.media.MediaRouter.RouteInfo!);
+ }
+
+ public abstract static class MediaRouter.ControlRequestCallback {
+ ctor public MediaRouter.ControlRequestCallback();
+ method public void onError(String!, android.os.Bundle!);
+ method public void onResult(android.os.Bundle!);
+ }
+
+ public static interface MediaRouter.OnPrepareTransferListener {
+ method @MainThread public com.google.common.util.concurrent.ListenableFuture<java.lang.Void!>? onPrepareTransfer(androidx.mediarouter.media.MediaRouter.RouteInfo, androidx.mediarouter.media.MediaRouter.RouteInfo);
+ }
+
+ public static final class MediaRouter.ProviderInfo {
+ method public android.content.ComponentName! getComponentName();
+ method public String! getPackageName();
+ method public androidx.mediarouter.media.MediaRouteProvider! getProviderInstance();
+ method public java.util.List<androidx.mediarouter.media.MediaRouter.RouteInfo!>! getRoutes();
+ }
+
+ public static class MediaRouter.RouteInfo {
+ method public boolean canDisconnect();
+ method public int getConnectionState();
+ method public java.util.List<android.content.IntentFilter!>! getControlFilters();
+ method public String? getDescription();
+ method public int getDeviceType();
+ method public android.os.Bundle? getExtras();
+ method public android.net.Uri! getIconUri();
+ method public String getId();
+ method public String! getName();
+ method public int getPlaybackStream();
+ method public int getPlaybackType();
+ method public android.view.Display? getPresentationDisplay();
+ method public androidx.mediarouter.media.MediaRouter.ProviderInfo! getProvider();
+ method public android.content.IntentSender? getSettingsIntent();
+ method public int getVolume();
+ method public int getVolumeHandling();
+ method public int getVolumeMax();
+ method public boolean isBluetooth();
+ method @Deprecated public boolean isConnecting();
+ method public boolean isDefault();
+ method public boolean isDeviceSpeaker();
+ method public boolean isEnabled();
+ method public boolean isSelected();
+ method public boolean matchesSelector(androidx.mediarouter.media.MediaRouteSelector);
+ method public void requestSetVolume(int);
+ method public void requestUpdateVolume(int);
+ method public void select();
+ method public void sendControlRequest(android.content.Intent, androidx.mediarouter.media.MediaRouter.ControlRequestCallback?);
+ method public boolean supportsControlAction(String, String);
+ method public boolean supportsControlCategory(String);
+ method public boolean supportsControlRequest(android.content.Intent);
+ field public static final int CONNECTION_STATE_CONNECTED = 2; // 0x2
+ field public static final int CONNECTION_STATE_CONNECTING = 1; // 0x1
+ field public static final int CONNECTION_STATE_DISCONNECTED = 0; // 0x0
+ field public static final int DEVICE_TYPE_SPEAKER = 2; // 0x2
+ field public static final int DEVICE_TYPE_TV = 1; // 0x1
+ field public static final int PLAYBACK_TYPE_LOCAL = 0; // 0x0
+ field public static final int PLAYBACK_TYPE_REMOTE = 1; // 0x1
+ field public static final int PLAYBACK_VOLUME_FIXED = 0; // 0x0
+ field public static final int PLAYBACK_VOLUME_VARIABLE = 1; // 0x1
+ }
+
+ public class MediaRouterParams {
+ method public int getDialogType();
+ method public boolean isOutputSwitcherEnabled();
+ method public boolean isTransferToLocalEnabled();
+ field public static final int DIALOG_TYPE_DEFAULT = 1; // 0x1
+ field public static final int DIALOG_TYPE_DYNAMIC_GROUP = 2; // 0x2
+ }
+
+ public static final class MediaRouterParams.Builder {
+ ctor public MediaRouterParams.Builder();
+ ctor public MediaRouterParams.Builder(androidx.mediarouter.media.MediaRouterParams);
+ method public androidx.mediarouter.media.MediaRouterParams build();
+ method public androidx.mediarouter.media.MediaRouterParams.Builder setDialogType(int);
+ method public androidx.mediarouter.media.MediaRouterParams.Builder setOutputSwitcherEnabled(boolean);
+ method public androidx.mediarouter.media.MediaRouterParams.Builder setTransferToLocalEnabled(boolean);
+ }
+
+ public final class MediaSessionStatus {
+ method public android.os.Bundle! asBundle();
+ method public static androidx.mediarouter.media.MediaSessionStatus! fromBundle(android.os.Bundle!);
+ method public android.os.Bundle! getExtras();
+ method public int getSessionState();
+ method public long getTimestamp();
+ method public boolean isQueuePaused();
+ field public static final int SESSION_STATE_ACTIVE = 0; // 0x0
+ field public static final int SESSION_STATE_ENDED = 1; // 0x1
+ field public static final int SESSION_STATE_INVALIDATED = 2; // 0x2
+ }
+
+ public static final class MediaSessionStatus.Builder {
+ ctor public MediaSessionStatus.Builder(int);
+ ctor public MediaSessionStatus.Builder(androidx.mediarouter.media.MediaSessionStatus!);
+ method public androidx.mediarouter.media.MediaSessionStatus! build();
+ method public androidx.mediarouter.media.MediaSessionStatus.Builder! setExtras(android.os.Bundle!);
+ method public androidx.mediarouter.media.MediaSessionStatus.Builder! setQueuePaused(boolean);
+ method public androidx.mediarouter.media.MediaSessionStatus.Builder! setSessionState(int);
+ method public androidx.mediarouter.media.MediaSessionStatus.Builder! setTimestamp(long);
+ }
+
+ public final class MediaTransferReceiver extends android.content.BroadcastReceiver {
+ ctor public MediaTransferReceiver();
+ method public void onReceive(android.content.Context, android.content.Intent);
+ }
+
+ public class RemotePlaybackClient {
+ ctor public RemotePlaybackClient(android.content.Context!, androidx.mediarouter.media.MediaRouter.RouteInfo!);
+ method public void endSession(android.os.Bundle!, androidx.mediarouter.media.RemotePlaybackClient.SessionActionCallback!);
+ method public void enqueue(android.net.Uri!, String!, android.os.Bundle!, long, android.os.Bundle!, androidx.mediarouter.media.RemotePlaybackClient.ItemActionCallback!);
+ method public String! getSessionId();
+ method public void getSessionStatus(android.os.Bundle!, androidx.mediarouter.media.RemotePlaybackClient.SessionActionCallback!);
+ method public void getStatus(String!, android.os.Bundle!, androidx.mediarouter.media.RemotePlaybackClient.ItemActionCallback!);
+ method public boolean hasSession();
+ method public boolean isMessagingSupported();
+ method public boolean isQueuingSupported();
+ method public boolean isRemotePlaybackSupported();
+ method public boolean isSessionManagementSupported();
+ method public void pause(android.os.Bundle!, androidx.mediarouter.media.RemotePlaybackClient.SessionActionCallback!);
+ method public void play(android.net.Uri!, String!, android.os.Bundle!, long, android.os.Bundle!, androidx.mediarouter.media.RemotePlaybackClient.ItemActionCallback!);
+ method public void release();
+ method public void remove(String!, android.os.Bundle!, androidx.mediarouter.media.RemotePlaybackClient.ItemActionCallback!);
+ method public void resume(android.os.Bundle!, androidx.mediarouter.media.RemotePlaybackClient.SessionActionCallback!);
+ method public void seek(String!, long, android.os.Bundle!, androidx.mediarouter.media.RemotePlaybackClient.ItemActionCallback!);
+ method public void sendMessage(android.os.Bundle!, androidx.mediarouter.media.RemotePlaybackClient.SessionActionCallback!);
+ method public void setOnMessageReceivedListener(androidx.mediarouter.media.RemotePlaybackClient.OnMessageReceivedListener!);
+ method public void setSessionId(String!);
+ method public void setStatusCallback(androidx.mediarouter.media.RemotePlaybackClient.StatusCallback!);
+ method public void startSession(android.os.Bundle!, androidx.mediarouter.media.RemotePlaybackClient.SessionActionCallback!);
+ method public void stop(android.os.Bundle!, androidx.mediarouter.media.RemotePlaybackClient.SessionActionCallback!);
+ }
+
+ public abstract static class RemotePlaybackClient.ActionCallback {
+ ctor public RemotePlaybackClient.ActionCallback();
+ method public void onError(String!, int, android.os.Bundle!);
+ }
+
+ public abstract static class RemotePlaybackClient.ItemActionCallback extends androidx.mediarouter.media.RemotePlaybackClient.ActionCallback {
+ ctor public RemotePlaybackClient.ItemActionCallback();
+ method public void onResult(android.os.Bundle!, String!, androidx.mediarouter.media.MediaSessionStatus!, String!, androidx.mediarouter.media.MediaItemStatus!);
+ }
+
+ public static interface RemotePlaybackClient.OnMessageReceivedListener {
+ method public void onMessageReceived(String!, android.os.Bundle!);
+ }
+
+ public abstract static class RemotePlaybackClient.SessionActionCallback extends androidx.mediarouter.media.RemotePlaybackClient.ActionCallback {
+ ctor public RemotePlaybackClient.SessionActionCallback();
+ method public void onResult(android.os.Bundle!, String!, androidx.mediarouter.media.MediaSessionStatus!);
+ }
+
+ public abstract static class RemotePlaybackClient.StatusCallback {
+ ctor public RemotePlaybackClient.StatusCallback();
+ method public void onItemStatusChanged(android.os.Bundle!, String!, androidx.mediarouter.media.MediaSessionStatus!, String!, androidx.mediarouter.media.MediaItemStatus!);
+ method public void onSessionChanged(String!);
+ method public void onSessionStatusChanged(android.os.Bundle!, String!, androidx.mediarouter.media.MediaSessionStatus!);
+ }
+
+}
+
diff --git a/mediarouter/mediarouter/api/public_plus_experimental_1.2.0-beta01.txt b/mediarouter/mediarouter/api/public_plus_experimental_1.2.0-beta01.txt
new file mode 100644
index 0000000..12b4c5c
--- /dev/null
+++ b/mediarouter/mediarouter/api/public_plus_experimental_1.2.0-beta01.txt
@@ -0,0 +1,550 @@
+// Signature format: 3.0
+package androidx.mediarouter.app {
+
+ public class MediaRouteActionProvider extends androidx.core.view.ActionProvider {
+ ctor public MediaRouteActionProvider(android.content.Context!);
+ method @Deprecated public void enableDynamicGroup();
+ method public androidx.mediarouter.app.MediaRouteDialogFactory getDialogFactory();
+ method public androidx.mediarouter.app.MediaRouteButton? getMediaRouteButton();
+ method public androidx.mediarouter.media.MediaRouteSelector getRouteSelector();
+ method public android.view.View! onCreateActionView();
+ method public androidx.mediarouter.app.MediaRouteButton! onCreateMediaRouteButton();
+ method public void setAlwaysVisible(boolean);
+ method public void setDialogFactory(androidx.mediarouter.app.MediaRouteDialogFactory);
+ method public void setRouteSelector(androidx.mediarouter.media.MediaRouteSelector);
+ }
+
+ public class MediaRouteButton extends android.view.View {
+ ctor public MediaRouteButton(android.content.Context!);
+ ctor public MediaRouteButton(android.content.Context!, android.util.AttributeSet!);
+ ctor public MediaRouteButton(android.content.Context!, android.util.AttributeSet!, int);
+ method @Deprecated public void enableDynamicGroup();
+ method public androidx.mediarouter.app.MediaRouteDialogFactory getDialogFactory();
+ method public androidx.mediarouter.media.MediaRouteSelector getRouteSelector();
+ method public void onAttachedToWindow();
+ method public void onDetachedFromWindow();
+ method public void setAlwaysVisible(boolean);
+ method public void setDialogFactory(androidx.mediarouter.app.MediaRouteDialogFactory);
+ method public void setRemoteIndicatorDrawable(android.graphics.drawable.Drawable!);
+ method public void setRouteSelector(androidx.mediarouter.media.MediaRouteSelector!);
+ method public boolean showDialog();
+ }
+
+ public class MediaRouteChooserDialog extends androidx.appcompat.app.AppCompatDialog {
+ ctor public MediaRouteChooserDialog(android.content.Context!);
+ ctor public MediaRouteChooserDialog(android.content.Context!, int);
+ method public androidx.mediarouter.media.MediaRouteSelector getRouteSelector();
+ method public boolean onFilterRoute(androidx.mediarouter.media.MediaRouter.RouteInfo);
+ method public void onFilterRoutes(java.util.List<androidx.mediarouter.media.MediaRouter.RouteInfo!>);
+ method public void refreshRoutes();
+ method public void setRouteSelector(androidx.mediarouter.media.MediaRouteSelector);
+ }
+
+ public class MediaRouteChooserDialogFragment extends androidx.fragment.app.DialogFragment {
+ ctor public MediaRouteChooserDialogFragment();
+ method public androidx.mediarouter.media.MediaRouteSelector! getRouteSelector();
+ method public androidx.mediarouter.app.MediaRouteChooserDialog! onCreateChooserDialog(android.content.Context!, android.os.Bundle!);
+ method public void setRouteSelector(androidx.mediarouter.media.MediaRouteSelector!);
+ }
+
+ public class MediaRouteControllerDialog extends androidx.appcompat.app.AlertDialog {
+ ctor public MediaRouteControllerDialog(android.content.Context!);
+ ctor public MediaRouteControllerDialog(android.content.Context!, int);
+ method public android.view.View! getMediaControlView();
+ method public android.support.v4.media.session.MediaSessionCompat.Token! getMediaSession();
+ method public androidx.mediarouter.media.MediaRouter.RouteInfo! getRoute();
+ method public boolean isVolumeControlEnabled();
+ method public android.view.View! onCreateMediaControlView(android.os.Bundle!);
+ method public void setVolumeControlEnabled(boolean);
+ }
+
+ public class MediaRouteControllerDialogFragment extends androidx.fragment.app.DialogFragment {
+ ctor public MediaRouteControllerDialogFragment();
+ method public androidx.mediarouter.app.MediaRouteControllerDialog! onCreateControllerDialog(android.content.Context!, android.os.Bundle!);
+ }
+
+ public class MediaRouteDialogFactory {
+ ctor public MediaRouteDialogFactory();
+ method public static androidx.mediarouter.app.MediaRouteDialogFactory getDefault();
+ method public androidx.mediarouter.app.MediaRouteChooserDialogFragment onCreateChooserDialogFragment();
+ method public androidx.mediarouter.app.MediaRouteControllerDialogFragment onCreateControllerDialogFragment();
+ }
+
+ public class MediaRouteDiscoveryFragment extends androidx.fragment.app.Fragment {
+ ctor public MediaRouteDiscoveryFragment();
+ method public androidx.mediarouter.media.MediaRouter! getMediaRouter();
+ method public androidx.mediarouter.media.MediaRouteSelector! getRouteSelector();
+ method public androidx.mediarouter.media.MediaRouter.Callback! onCreateCallback();
+ method public int onPrepareCallbackFlags();
+ method public void setRouteSelector(androidx.mediarouter.media.MediaRouteSelector!);
+ }
+
+}
+
+package androidx.mediarouter.media {
+
+ public final class MediaControlIntent {
+ field public static final String ACTION_END_SESSION = "android.media.intent.action.END_SESSION";
+ field public static final String ACTION_ENQUEUE = "android.media.intent.action.ENQUEUE";
+ field public static final String ACTION_GET_SESSION_STATUS = "android.media.intent.action.GET_SESSION_STATUS";
+ field public static final String ACTION_GET_STATUS = "android.media.intent.action.GET_STATUS";
+ field public static final String ACTION_PAUSE = "android.media.intent.action.PAUSE";
+ field public static final String ACTION_PLAY = "android.media.intent.action.PLAY";
+ field public static final String ACTION_REMOVE = "android.media.intent.action.REMOVE";
+ field public static final String ACTION_RESUME = "android.media.intent.action.RESUME";
+ field public static final String ACTION_SEEK = "android.media.intent.action.SEEK";
+ field public static final String ACTION_SEND_MESSAGE = "android.media.intent.action.SEND_MESSAGE";
+ field public static final String ACTION_START_SESSION = "android.media.intent.action.START_SESSION";
+ field public static final String ACTION_STOP = "android.media.intent.action.STOP";
+ field public static final String CATEGORY_LIVE_AUDIO = "android.media.intent.category.LIVE_AUDIO";
+ field public static final String CATEGORY_LIVE_VIDEO = "android.media.intent.category.LIVE_VIDEO";
+ field public static final String CATEGORY_REMOTE_PLAYBACK = "android.media.intent.category.REMOTE_PLAYBACK";
+ field public static final int ERROR_INVALID_ITEM_ID = 3; // 0x3
+ field public static final int ERROR_INVALID_SESSION_ID = 2; // 0x2
+ field public static final int ERROR_UNKNOWN = 0; // 0x0
+ field public static final int ERROR_UNSUPPORTED_OPERATION = 1; // 0x1
+ field public static final String EXTRA_ERROR_CODE = "android.media.intent.extra.ERROR_CODE";
+ field public static final String EXTRA_ITEM_CONTENT_POSITION = "android.media.intent.extra.ITEM_POSITION";
+ field public static final String EXTRA_ITEM_HTTP_HEADERS = "android.media.intent.extra.HTTP_HEADERS";
+ field public static final String EXTRA_ITEM_ID = "android.media.intent.extra.ITEM_ID";
+ field public static final String EXTRA_ITEM_METADATA = "android.media.intent.extra.ITEM_METADATA";
+ field public static final String EXTRA_ITEM_STATUS = "android.media.intent.extra.ITEM_STATUS";
+ field public static final String EXTRA_ITEM_STATUS_UPDATE_RECEIVER = "android.media.intent.extra.ITEM_STATUS_UPDATE_RECEIVER";
+ field public static final String EXTRA_MESSAGE = "android.media.intent.extra.MESSAGE";
+ field public static final String EXTRA_MESSAGE_RECEIVER = "android.media.intent.extra.MESSAGE_RECEIVER";
+ field public static final String EXTRA_SESSION_ID = "android.media.intent.extra.SESSION_ID";
+ field public static final String EXTRA_SESSION_STATUS = "android.media.intent.extra.SESSION_STATUS";
+ field public static final String EXTRA_SESSION_STATUS_UPDATE_RECEIVER = "android.media.intent.extra.SESSION_STATUS_UPDATE_RECEIVER";
+ }
+
+ public final class MediaItemMetadata {
+ field public static final String KEY_ALBUM_ARTIST = "android.media.metadata.ALBUM_ARTIST";
+ field public static final String KEY_ALBUM_TITLE = "android.media.metadata.ALBUM_TITLE";
+ field public static final String KEY_ARTIST = "android.media.metadata.ARTIST";
+ field public static final String KEY_ARTWORK_URI = "android.media.metadata.ARTWORK_URI";
+ field public static final String KEY_AUTHOR = "android.media.metadata.AUTHOR";
+ field public static final String KEY_COMPOSER = "android.media.metadata.COMPOSER";
+ field public static final String KEY_DISC_NUMBER = "android.media.metadata.DISC_NUMBER";
+ field public static final String KEY_DURATION = "android.media.metadata.DURATION";
+ field public static final String KEY_TITLE = "android.media.metadata.TITLE";
+ field public static final String KEY_TRACK_NUMBER = "android.media.metadata.TRACK_NUMBER";
+ field public static final String KEY_YEAR = "android.media.metadata.YEAR";
+ }
+
+ public final class MediaItemStatus {
+ method public android.os.Bundle! asBundle();
+ method public static androidx.mediarouter.media.MediaItemStatus! fromBundle(android.os.Bundle!);
+ method public long getContentDuration();
+ method public long getContentPosition();
+ method public android.os.Bundle! getExtras();
+ method public int getPlaybackState();
+ method public long getTimestamp();
+ field public static final String EXTRA_HTTP_RESPONSE_HEADERS = "android.media.status.extra.HTTP_RESPONSE_HEADERS";
+ field public static final String EXTRA_HTTP_STATUS_CODE = "android.media.status.extra.HTTP_STATUS_CODE";
+ field public static final int PLAYBACK_STATE_BUFFERING = 3; // 0x3
+ field public static final int PLAYBACK_STATE_CANCELED = 5; // 0x5
+ field public static final int PLAYBACK_STATE_ERROR = 7; // 0x7
+ field public static final int PLAYBACK_STATE_FINISHED = 4; // 0x4
+ field public static final int PLAYBACK_STATE_INVALIDATED = 6; // 0x6
+ field public static final int PLAYBACK_STATE_PAUSED = 2; // 0x2
+ field public static final int PLAYBACK_STATE_PENDING = 0; // 0x0
+ field public static final int PLAYBACK_STATE_PLAYING = 1; // 0x1
+ }
+
+ public static final class MediaItemStatus.Builder {
+ ctor public MediaItemStatus.Builder(int);
+ ctor public MediaItemStatus.Builder(androidx.mediarouter.media.MediaItemStatus!);
+ method public androidx.mediarouter.media.MediaItemStatus! build();
+ method public androidx.mediarouter.media.MediaItemStatus.Builder! setContentDuration(long);
+ method public androidx.mediarouter.media.MediaItemStatus.Builder! setContentPosition(long);
+ method public androidx.mediarouter.media.MediaItemStatus.Builder! setExtras(android.os.Bundle!);
+ method public androidx.mediarouter.media.MediaItemStatus.Builder! setPlaybackState(int);
+ method public androidx.mediarouter.media.MediaItemStatus.Builder! setTimestamp(long);
+ }
+
+ public final class MediaRouteDescriptor {
+ method public android.os.Bundle! asBundle();
+ method public boolean canDisconnectAndKeepPlaying();
+ method public static androidx.mediarouter.media.MediaRouteDescriptor! fromBundle(android.os.Bundle!);
+ method public int getConnectionState();
+ method public java.util.List<android.content.IntentFilter!>! getControlFilters();
+ method public String! getDescription();
+ method public int getDeviceType();
+ method public android.os.Bundle! getExtras();
+ method public android.net.Uri! getIconUri();
+ method public String! getId();
+ method public String! getName();
+ method public int getPlaybackStream();
+ method public int getPlaybackType();
+ method public int getPresentationDisplayId();
+ method public android.content.IntentSender! getSettingsActivity();
+ method public int getVolume();
+ method public int getVolumeHandling();
+ method public int getVolumeMax();
+ method @Deprecated public boolean isConnecting();
+ method public boolean isDynamicGroupRoute();
+ method public boolean isEnabled();
+ method public boolean isValid();
+ }
+
+ public static final class MediaRouteDescriptor.Builder {
+ ctor public MediaRouteDescriptor.Builder(String!, String!);
+ ctor public MediaRouteDescriptor.Builder(androidx.mediarouter.media.MediaRouteDescriptor!);
+ method public androidx.mediarouter.media.MediaRouteDescriptor.Builder! addControlFilter(android.content.IntentFilter!);
+ method public androidx.mediarouter.media.MediaRouteDescriptor.Builder! addControlFilters(java.util.Collection<android.content.IntentFilter!>!);
+ method public androidx.mediarouter.media.MediaRouteDescriptor! build();
+ method public androidx.mediarouter.media.MediaRouteDescriptor.Builder! setCanDisconnect(boolean);
+ method @Deprecated public androidx.mediarouter.media.MediaRouteDescriptor.Builder! setConnecting(boolean);
+ method public androidx.mediarouter.media.MediaRouteDescriptor.Builder! setConnectionState(int);
+ method public androidx.mediarouter.media.MediaRouteDescriptor.Builder! setDescription(String!);
+ method public androidx.mediarouter.media.MediaRouteDescriptor.Builder! setDeviceType(int);
+ method public androidx.mediarouter.media.MediaRouteDescriptor.Builder! setEnabled(boolean);
+ method public androidx.mediarouter.media.MediaRouteDescriptor.Builder! setExtras(android.os.Bundle!);
+ method public androidx.mediarouter.media.MediaRouteDescriptor.Builder! setIconUri(android.net.Uri!);
+ method public androidx.mediarouter.media.MediaRouteDescriptor.Builder! setId(String!);
+ method public androidx.mediarouter.media.MediaRouteDescriptor.Builder! setIsDynamicGroupRoute(boolean);
+ method public androidx.mediarouter.media.MediaRouteDescriptor.Builder! setName(String!);
+ method public androidx.mediarouter.media.MediaRouteDescriptor.Builder! setPlaybackStream(int);
+ method public androidx.mediarouter.media.MediaRouteDescriptor.Builder! setPlaybackType(int);
+ method public androidx.mediarouter.media.MediaRouteDescriptor.Builder! setPresentationDisplayId(int);
+ method public androidx.mediarouter.media.MediaRouteDescriptor.Builder! setSettingsActivity(android.content.IntentSender!);
+ method public androidx.mediarouter.media.MediaRouteDescriptor.Builder! setVolume(int);
+ method public androidx.mediarouter.media.MediaRouteDescriptor.Builder! setVolumeHandling(int);
+ method public androidx.mediarouter.media.MediaRouteDescriptor.Builder! setVolumeMax(int);
+ }
+
+ public final class MediaRouteDiscoveryRequest {
+ ctor public MediaRouteDiscoveryRequest(androidx.mediarouter.media.MediaRouteSelector!, boolean);
+ method public android.os.Bundle! asBundle();
+ method public static androidx.mediarouter.media.MediaRouteDiscoveryRequest! fromBundle(android.os.Bundle!);
+ method public androidx.mediarouter.media.MediaRouteSelector! getSelector();
+ method public boolean isActiveScan();
+ method public boolean isValid();
+ }
+
+ public abstract class MediaRouteProvider {
+ ctor public MediaRouteProvider(android.content.Context);
+ method public final android.content.Context! getContext();
+ method public final androidx.mediarouter.media.MediaRouteProviderDescriptor? getDescriptor();
+ method public final androidx.mediarouter.media.MediaRouteDiscoveryRequest? getDiscoveryRequest();
+ method public final android.os.Handler! getHandler();
+ method public final androidx.mediarouter.media.MediaRouteProvider.ProviderMetadata! getMetadata();
+ method public androidx.mediarouter.media.MediaRouteProvider.DynamicGroupRouteController? onCreateDynamicGroupRouteController(String);
+ method public androidx.mediarouter.media.MediaRouteProvider.RouteController? onCreateRouteController(String);
+ method public void onDiscoveryRequestChanged(androidx.mediarouter.media.MediaRouteDiscoveryRequest?);
+ method public final void setCallback(androidx.mediarouter.media.MediaRouteProvider.Callback?);
+ method public final void setDescriptor(androidx.mediarouter.media.MediaRouteProviderDescriptor?);
+ method public final void setDiscoveryRequest(androidx.mediarouter.media.MediaRouteDiscoveryRequest!);
+ }
+
+ public abstract static class MediaRouteProvider.Callback {
+ ctor public MediaRouteProvider.Callback();
+ method public void onDescriptorChanged(androidx.mediarouter.media.MediaRouteProvider, androidx.mediarouter.media.MediaRouteProviderDescriptor?);
+ }
+
+ public abstract static class MediaRouteProvider.DynamicGroupRouteController extends androidx.mediarouter.media.MediaRouteProvider.RouteController {
+ ctor public MediaRouteProvider.DynamicGroupRouteController();
+ method public String? getGroupableSelectionTitle();
+ method public String? getTransferableSectionTitle();
+ method @Deprecated public final void notifyDynamicRoutesChanged(java.util.Collection<androidx.mediarouter.media.MediaRouteProvider.DynamicGroupRouteController.DynamicRouteDescriptor!>!);
+ method public final void notifyDynamicRoutesChanged(androidx.mediarouter.media.MediaRouteDescriptor, java.util.Collection<androidx.mediarouter.media.MediaRouteProvider.DynamicGroupRouteController.DynamicRouteDescriptor!>);
+ method public abstract void onAddMemberRoute(String);
+ method public abstract void onRemoveMemberRoute(String!);
+ method public abstract void onUpdateMemberRoutes(java.util.List<java.lang.String!>?);
+ }
+
+ public static final class MediaRouteProvider.DynamicGroupRouteController.DynamicRouteDescriptor {
+ method public androidx.mediarouter.media.MediaRouteDescriptor getRouteDescriptor();
+ method public int getSelectionState();
+ method public boolean isGroupable();
+ method public boolean isTransferable();
+ method public boolean isUnselectable();
+ field public static final int SELECTED = 3; // 0x3
+ field public static final int SELECTING = 2; // 0x2
+ field public static final int UNSELECTED = 1; // 0x1
+ field public static final int UNSELECTING = 0; // 0x0
+ }
+
+ public static final class MediaRouteProvider.DynamicGroupRouteController.DynamicRouteDescriptor.Builder {
+ ctor public MediaRouteProvider.DynamicGroupRouteController.DynamicRouteDescriptor.Builder(androidx.mediarouter.media.MediaRouteDescriptor!);
+ ctor public MediaRouteProvider.DynamicGroupRouteController.DynamicRouteDescriptor.Builder(androidx.mediarouter.media.MediaRouteProvider.DynamicGroupRouteController.DynamicRouteDescriptor!);
+ method public androidx.mediarouter.media.MediaRouteProvider.DynamicGroupRouteController.DynamicRouteDescriptor! build();
+ method public androidx.mediarouter.media.MediaRouteProvider.DynamicGroupRouteController.DynamicRouteDescriptor.Builder! setIsGroupable(boolean);
+ method public androidx.mediarouter.media.MediaRouteProvider.DynamicGroupRouteController.DynamicRouteDescriptor.Builder! setIsTransferable(boolean);
+ method public androidx.mediarouter.media.MediaRouteProvider.DynamicGroupRouteController.DynamicRouteDescriptor.Builder! setIsUnselectable(boolean);
+ method public androidx.mediarouter.media.MediaRouteProvider.DynamicGroupRouteController.DynamicRouteDescriptor.Builder! setSelectionState(int);
+ }
+
+ public static final class MediaRouteProvider.ProviderMetadata {
+ method public android.content.ComponentName! getComponentName();
+ method public String! getPackageName();
+ }
+
+ public abstract static class MediaRouteProvider.RouteController {
+ ctor public MediaRouteProvider.RouteController();
+ method public boolean onControlRequest(android.content.Intent!, androidx.mediarouter.media.MediaRouter.ControlRequestCallback?);
+ method public void onRelease();
+ method public void onSelect();
+ method public void onSetVolume(int);
+ method @Deprecated public void onUnselect();
+ method public void onUnselect(int);
+ method public void onUpdateVolume(int);
+ }
+
+ public final class MediaRouteProviderDescriptor {
+ method public android.os.Bundle! asBundle();
+ method public static androidx.mediarouter.media.MediaRouteProviderDescriptor! fromBundle(android.os.Bundle!);
+ method public java.util.List<androidx.mediarouter.media.MediaRouteDescriptor!> getRoutes();
+ method public boolean isValid();
+ method public boolean supportsDynamicGroupRoute();
+ }
+
+ public static final class MediaRouteProviderDescriptor.Builder {
+ ctor public MediaRouteProviderDescriptor.Builder();
+ ctor public MediaRouteProviderDescriptor.Builder(androidx.mediarouter.media.MediaRouteProviderDescriptor!);
+ method public androidx.mediarouter.media.MediaRouteProviderDescriptor.Builder! addRoute(androidx.mediarouter.media.MediaRouteDescriptor!);
+ method public androidx.mediarouter.media.MediaRouteProviderDescriptor.Builder! addRoutes(java.util.Collection<androidx.mediarouter.media.MediaRouteDescriptor!>!);
+ method public androidx.mediarouter.media.MediaRouteProviderDescriptor! build();
+ method public androidx.mediarouter.media.MediaRouteProviderDescriptor.Builder! setSupportsDynamicGroupRoute(boolean);
+ }
+
+ public abstract class MediaRouteProviderService extends android.app.Service {
+ ctor public MediaRouteProviderService();
+ method public androidx.mediarouter.media.MediaRouteProvider! getMediaRouteProvider();
+ method public android.os.IBinder! onBind(android.content.Intent!);
+ method public abstract androidx.mediarouter.media.MediaRouteProvider! onCreateMediaRouteProvider();
+ field public static final String SERVICE_INTERFACE = "android.media.MediaRouteProviderService";
+ }
+
+ public final class MediaRouteSelector {
+ method public android.os.Bundle! asBundle();
+ method public boolean contains(androidx.mediarouter.media.MediaRouteSelector!);
+ method public static androidx.mediarouter.media.MediaRouteSelector! fromBundle(android.os.Bundle?);
+ method public java.util.List<java.lang.String!>! getControlCategories();
+ method public boolean hasControlCategory(String!);
+ method public boolean isEmpty();
+ method public boolean isValid();
+ method public boolean matchesControlFilters(java.util.List<android.content.IntentFilter!>!);
+ field public static final androidx.mediarouter.media.MediaRouteSelector! EMPTY;
+ }
+
+ public static final class MediaRouteSelector.Builder {
+ ctor public MediaRouteSelector.Builder();
+ ctor public MediaRouteSelector.Builder(androidx.mediarouter.media.MediaRouteSelector);
+ method public androidx.mediarouter.media.MediaRouteSelector.Builder addControlCategories(java.util.Collection<java.lang.String!>);
+ method public androidx.mediarouter.media.MediaRouteSelector.Builder addControlCategory(String);
+ method public androidx.mediarouter.media.MediaRouteSelector.Builder addSelector(androidx.mediarouter.media.MediaRouteSelector);
+ method public androidx.mediarouter.media.MediaRouteSelector build();
+ }
+
+ public final class MediaRouter {
+ method public void addCallback(androidx.mediarouter.media.MediaRouteSelector!, androidx.mediarouter.media.MediaRouter.Callback!);
+ method public void addCallback(androidx.mediarouter.media.MediaRouteSelector, androidx.mediarouter.media.MediaRouter.Callback, int);
+ method public void addProvider(androidx.mediarouter.media.MediaRouteProvider);
+ method public void addRemoteControlClient(Object);
+ method public androidx.mediarouter.media.MediaRouter.RouteInfo! getBluetoothRoute();
+ method public androidx.mediarouter.media.MediaRouter.RouteInfo getDefaultRoute();
+ method public static androidx.mediarouter.media.MediaRouter! getInstance(android.content.Context);
+ method public android.support.v4.media.session.MediaSessionCompat.Token! getMediaSessionToken();
+ method public java.util.List<androidx.mediarouter.media.MediaRouter.ProviderInfo!>! getProviders();
+ method public androidx.mediarouter.media.MediaRouterParams? getRouterParams();
+ method public java.util.List<androidx.mediarouter.media.MediaRouter.RouteInfo!>! getRoutes();
+ method public androidx.mediarouter.media.MediaRouter.RouteInfo getSelectedRoute();
+ method public boolean isRouteAvailable(androidx.mediarouter.media.MediaRouteSelector, int);
+ method public void removeCallback(androidx.mediarouter.media.MediaRouter.Callback);
+ method public void removeProvider(androidx.mediarouter.media.MediaRouteProvider);
+ method public void removeRemoteControlClient(Object);
+ method public void selectRoute(androidx.mediarouter.media.MediaRouter.RouteInfo);
+ method public void setMediaSession(Object!);
+ method public void setMediaSessionCompat(android.support.v4.media.session.MediaSessionCompat!);
+ method @MainThread public void setOnPrepareTransferListener(androidx.mediarouter.media.MediaRouter.OnPrepareTransferListener?);
+ method public void setRouterParams(androidx.mediarouter.media.MediaRouterParams?);
+ method public void unselect(int);
+ method public androidx.mediarouter.media.MediaRouter.RouteInfo updateSelectedRoute(androidx.mediarouter.media.MediaRouteSelector);
+ field public static final int AVAILABILITY_FLAG_IGNORE_DEFAULT_ROUTE = 1; // 0x1
+ field public static final int AVAILABILITY_FLAG_REQUIRE_MATCH = 2; // 0x2
+ field public static final int CALLBACK_FLAG_FORCE_DISCOVERY = 8; // 0x8
+ field public static final int CALLBACK_FLAG_PERFORM_ACTIVE_SCAN = 1; // 0x1
+ field public static final int CALLBACK_FLAG_REQUEST_DISCOVERY = 4; // 0x4
+ field public static final int CALLBACK_FLAG_UNFILTERED_EVENTS = 2; // 0x2
+ field public static final int UNSELECT_REASON_DISCONNECTED = 1; // 0x1
+ field public static final int UNSELECT_REASON_ROUTE_CHANGED = 3; // 0x3
+ field public static final int UNSELECT_REASON_STOPPED = 2; // 0x2
+ field public static final int UNSELECT_REASON_UNKNOWN = 0; // 0x0
+ }
+
+ public abstract static class MediaRouter.Callback {
+ ctor public MediaRouter.Callback();
+ method public void onProviderAdded(androidx.mediarouter.media.MediaRouter!, androidx.mediarouter.media.MediaRouter.ProviderInfo!);
+ method public void onProviderChanged(androidx.mediarouter.media.MediaRouter!, androidx.mediarouter.media.MediaRouter.ProviderInfo!);
+ method public void onProviderRemoved(androidx.mediarouter.media.MediaRouter!, androidx.mediarouter.media.MediaRouter.ProviderInfo!);
+ method public void onRouteAdded(androidx.mediarouter.media.MediaRouter!, androidx.mediarouter.media.MediaRouter.RouteInfo!);
+ method public void onRouteChanged(androidx.mediarouter.media.MediaRouter!, androidx.mediarouter.media.MediaRouter.RouteInfo!);
+ method public void onRoutePresentationDisplayChanged(androidx.mediarouter.media.MediaRouter!, androidx.mediarouter.media.MediaRouter.RouteInfo!);
+ method public void onRouteRemoved(androidx.mediarouter.media.MediaRouter!, androidx.mediarouter.media.MediaRouter.RouteInfo!);
+ method @Deprecated public void onRouteSelected(androidx.mediarouter.media.MediaRouter!, androidx.mediarouter.media.MediaRouter.RouteInfo!);
+ method public void onRouteSelected(androidx.mediarouter.media.MediaRouter, androidx.mediarouter.media.MediaRouter.RouteInfo, int);
+ method public void onRouteSelected(androidx.mediarouter.media.MediaRouter, androidx.mediarouter.media.MediaRouter.RouteInfo, int, androidx.mediarouter.media.MediaRouter.RouteInfo);
+ method @Deprecated public void onRouteUnselected(androidx.mediarouter.media.MediaRouter!, androidx.mediarouter.media.MediaRouter.RouteInfo!);
+ method public void onRouteUnselected(androidx.mediarouter.media.MediaRouter!, androidx.mediarouter.media.MediaRouter.RouteInfo!, int);
+ method public void onRouteVolumeChanged(androidx.mediarouter.media.MediaRouter!, androidx.mediarouter.media.MediaRouter.RouteInfo!);
+ }
+
+ public abstract static class MediaRouter.ControlRequestCallback {
+ ctor public MediaRouter.ControlRequestCallback();
+ method public void onError(String!, android.os.Bundle!);
+ method public void onResult(android.os.Bundle!);
+ }
+
+ public static interface MediaRouter.OnPrepareTransferListener {
+ method @MainThread public com.google.common.util.concurrent.ListenableFuture<java.lang.Void!>? onPrepareTransfer(androidx.mediarouter.media.MediaRouter.RouteInfo, androidx.mediarouter.media.MediaRouter.RouteInfo);
+ }
+
+ public static final class MediaRouter.ProviderInfo {
+ method public android.content.ComponentName! getComponentName();
+ method public String! getPackageName();
+ method public androidx.mediarouter.media.MediaRouteProvider! getProviderInstance();
+ method public java.util.List<androidx.mediarouter.media.MediaRouter.RouteInfo!>! getRoutes();
+ }
+
+ public static class MediaRouter.RouteInfo {
+ method public boolean canDisconnect();
+ method public int getConnectionState();
+ method public java.util.List<android.content.IntentFilter!>! getControlFilters();
+ method public String? getDescription();
+ method public int getDeviceType();
+ method public android.os.Bundle? getExtras();
+ method public android.net.Uri! getIconUri();
+ method public String getId();
+ method public String! getName();
+ method public int getPlaybackStream();
+ method public int getPlaybackType();
+ method public android.view.Display? getPresentationDisplay();
+ method public androidx.mediarouter.media.MediaRouter.ProviderInfo! getProvider();
+ method public android.content.IntentSender? getSettingsIntent();
+ method public int getVolume();
+ method public int getVolumeHandling();
+ method public int getVolumeMax();
+ method public boolean isBluetooth();
+ method @Deprecated public boolean isConnecting();
+ method public boolean isDefault();
+ method public boolean isDeviceSpeaker();
+ method public boolean isEnabled();
+ method public boolean isSelected();
+ method public boolean matchesSelector(androidx.mediarouter.media.MediaRouteSelector);
+ method public void requestSetVolume(int);
+ method public void requestUpdateVolume(int);
+ method public void select();
+ method public void sendControlRequest(android.content.Intent, androidx.mediarouter.media.MediaRouter.ControlRequestCallback?);
+ method public boolean supportsControlAction(String, String);
+ method public boolean supportsControlCategory(String);
+ method public boolean supportsControlRequest(android.content.Intent);
+ field public static final int CONNECTION_STATE_CONNECTED = 2; // 0x2
+ field public static final int CONNECTION_STATE_CONNECTING = 1; // 0x1
+ field public static final int CONNECTION_STATE_DISCONNECTED = 0; // 0x0
+ field public static final int DEVICE_TYPE_SPEAKER = 2; // 0x2
+ field public static final int DEVICE_TYPE_TV = 1; // 0x1
+ field public static final int PLAYBACK_TYPE_LOCAL = 0; // 0x0
+ field public static final int PLAYBACK_TYPE_REMOTE = 1; // 0x1
+ field public static final int PLAYBACK_VOLUME_FIXED = 0; // 0x0
+ field public static final int PLAYBACK_VOLUME_VARIABLE = 1; // 0x1
+ }
+
+ public class MediaRouterParams {
+ method public int getDialogType();
+ method public boolean isOutputSwitcherEnabled();
+ method public boolean isTransferToLocalEnabled();
+ field public static final int DIALOG_TYPE_DEFAULT = 1; // 0x1
+ field public static final int DIALOG_TYPE_DYNAMIC_GROUP = 2; // 0x2
+ }
+
+ public static final class MediaRouterParams.Builder {
+ ctor public MediaRouterParams.Builder();
+ ctor public MediaRouterParams.Builder(androidx.mediarouter.media.MediaRouterParams);
+ method public androidx.mediarouter.media.MediaRouterParams build();
+ method public androidx.mediarouter.media.MediaRouterParams.Builder setDialogType(int);
+ method public androidx.mediarouter.media.MediaRouterParams.Builder setOutputSwitcherEnabled(boolean);
+ method public androidx.mediarouter.media.MediaRouterParams.Builder setTransferToLocalEnabled(boolean);
+ }
+
+ public final class MediaSessionStatus {
+ method public android.os.Bundle! asBundle();
+ method public static androidx.mediarouter.media.MediaSessionStatus! fromBundle(android.os.Bundle!);
+ method public android.os.Bundle! getExtras();
+ method public int getSessionState();
+ method public long getTimestamp();
+ method public boolean isQueuePaused();
+ field public static final int SESSION_STATE_ACTIVE = 0; // 0x0
+ field public static final int SESSION_STATE_ENDED = 1; // 0x1
+ field public static final int SESSION_STATE_INVALIDATED = 2; // 0x2
+ }
+
+ public static final class MediaSessionStatus.Builder {
+ ctor public MediaSessionStatus.Builder(int);
+ ctor public MediaSessionStatus.Builder(androidx.mediarouter.media.MediaSessionStatus!);
+ method public androidx.mediarouter.media.MediaSessionStatus! build();
+ method public androidx.mediarouter.media.MediaSessionStatus.Builder! setExtras(android.os.Bundle!);
+ method public androidx.mediarouter.media.MediaSessionStatus.Builder! setQueuePaused(boolean);
+ method public androidx.mediarouter.media.MediaSessionStatus.Builder! setSessionState(int);
+ method public androidx.mediarouter.media.MediaSessionStatus.Builder! setTimestamp(long);
+ }
+
+ public final class MediaTransferReceiver extends android.content.BroadcastReceiver {
+ ctor public MediaTransferReceiver();
+ method public void onReceive(android.content.Context, android.content.Intent);
+ }
+
+ public class RemotePlaybackClient {
+ ctor public RemotePlaybackClient(android.content.Context!, androidx.mediarouter.media.MediaRouter.RouteInfo!);
+ method public void endSession(android.os.Bundle!, androidx.mediarouter.media.RemotePlaybackClient.SessionActionCallback!);
+ method public void enqueue(android.net.Uri!, String!, android.os.Bundle!, long, android.os.Bundle!, androidx.mediarouter.media.RemotePlaybackClient.ItemActionCallback!);
+ method public String! getSessionId();
+ method public void getSessionStatus(android.os.Bundle!, androidx.mediarouter.media.RemotePlaybackClient.SessionActionCallback!);
+ method public void getStatus(String!, android.os.Bundle!, androidx.mediarouter.media.RemotePlaybackClient.ItemActionCallback!);
+ method public boolean hasSession();
+ method public boolean isMessagingSupported();
+ method public boolean isQueuingSupported();
+ method public boolean isRemotePlaybackSupported();
+ method public boolean isSessionManagementSupported();
+ method public void pause(android.os.Bundle!, androidx.mediarouter.media.RemotePlaybackClient.SessionActionCallback!);
+ method public void play(android.net.Uri!, String!, android.os.Bundle!, long, android.os.Bundle!, androidx.mediarouter.media.RemotePlaybackClient.ItemActionCallback!);
+ method public void release();
+ method public void remove(String!, android.os.Bundle!, androidx.mediarouter.media.RemotePlaybackClient.ItemActionCallback!);
+ method public void resume(android.os.Bundle!, androidx.mediarouter.media.RemotePlaybackClient.SessionActionCallback!);
+ method public void seek(String!, long, android.os.Bundle!, androidx.mediarouter.media.RemotePlaybackClient.ItemActionCallback!);
+ method public void sendMessage(android.os.Bundle!, androidx.mediarouter.media.RemotePlaybackClient.SessionActionCallback!);
+ method public void setOnMessageReceivedListener(androidx.mediarouter.media.RemotePlaybackClient.OnMessageReceivedListener!);
+ method public void setSessionId(String!);
+ method public void setStatusCallback(androidx.mediarouter.media.RemotePlaybackClient.StatusCallback!);
+ method public void startSession(android.os.Bundle!, androidx.mediarouter.media.RemotePlaybackClient.SessionActionCallback!);
+ method public void stop(android.os.Bundle!, androidx.mediarouter.media.RemotePlaybackClient.SessionActionCallback!);
+ }
+
+ public abstract static class RemotePlaybackClient.ActionCallback {
+ ctor public RemotePlaybackClient.ActionCallback();
+ method public void onError(String!, int, android.os.Bundle!);
+ }
+
+ public abstract static class RemotePlaybackClient.ItemActionCallback extends androidx.mediarouter.media.RemotePlaybackClient.ActionCallback {
+ ctor public RemotePlaybackClient.ItemActionCallback();
+ method public void onResult(android.os.Bundle!, String!, androidx.mediarouter.media.MediaSessionStatus!, String!, androidx.mediarouter.media.MediaItemStatus!);
+ }
+
+ public static interface RemotePlaybackClient.OnMessageReceivedListener {
+ method public void onMessageReceived(String!, android.os.Bundle!);
+ }
+
+ public abstract static class RemotePlaybackClient.SessionActionCallback extends androidx.mediarouter.media.RemotePlaybackClient.ActionCallback {
+ ctor public RemotePlaybackClient.SessionActionCallback();
+ method public void onResult(android.os.Bundle!, String!, androidx.mediarouter.media.MediaSessionStatus!);
+ }
+
+ public abstract static class RemotePlaybackClient.StatusCallback {
+ ctor public RemotePlaybackClient.StatusCallback();
+ method public void onItemStatusChanged(android.os.Bundle!, String!, androidx.mediarouter.media.MediaSessionStatus!, String!, androidx.mediarouter.media.MediaItemStatus!);
+ method public void onSessionChanged(String!);
+ method public void onSessionStatusChanged(android.os.Bundle!, String!, androidx.mediarouter.media.MediaSessionStatus!);
+ }
+
+}
+
diff --git a/mediarouter/mediarouter/api/res-1.2.0-beta01.txt b/mediarouter/mediarouter/api/res-1.2.0-beta01.txt
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/mediarouter/mediarouter/api/res-1.2.0-beta01.txt
diff --git a/mediarouter/mediarouter/api/restricted_1.2.0-beta01.txt b/mediarouter/mediarouter/api/restricted_1.2.0-beta01.txt
new file mode 100644
index 0000000..12b4c5c
--- /dev/null
+++ b/mediarouter/mediarouter/api/restricted_1.2.0-beta01.txt
@@ -0,0 +1,550 @@
+// Signature format: 3.0
+package androidx.mediarouter.app {
+
+ public class MediaRouteActionProvider extends androidx.core.view.ActionProvider {
+ ctor public MediaRouteActionProvider(android.content.Context!);
+ method @Deprecated public void enableDynamicGroup();
+ method public androidx.mediarouter.app.MediaRouteDialogFactory getDialogFactory();
+ method public androidx.mediarouter.app.MediaRouteButton? getMediaRouteButton();
+ method public androidx.mediarouter.media.MediaRouteSelector getRouteSelector();
+ method public android.view.View! onCreateActionView();
+ method public androidx.mediarouter.app.MediaRouteButton! onCreateMediaRouteButton();
+ method public void setAlwaysVisible(boolean);
+ method public void setDialogFactory(androidx.mediarouter.app.MediaRouteDialogFactory);
+ method public void setRouteSelector(androidx.mediarouter.media.MediaRouteSelector);
+ }
+
+ public class MediaRouteButton extends android.view.View {
+ ctor public MediaRouteButton(android.content.Context!);
+ ctor public MediaRouteButton(android.content.Context!, android.util.AttributeSet!);
+ ctor public MediaRouteButton(android.content.Context!, android.util.AttributeSet!, int);
+ method @Deprecated public void enableDynamicGroup();
+ method public androidx.mediarouter.app.MediaRouteDialogFactory getDialogFactory();
+ method public androidx.mediarouter.media.MediaRouteSelector getRouteSelector();
+ method public void onAttachedToWindow();
+ method public void onDetachedFromWindow();
+ method public void setAlwaysVisible(boolean);
+ method public void setDialogFactory(androidx.mediarouter.app.MediaRouteDialogFactory);
+ method public void setRemoteIndicatorDrawable(android.graphics.drawable.Drawable!);
+ method public void setRouteSelector(androidx.mediarouter.media.MediaRouteSelector!);
+ method public boolean showDialog();
+ }
+
+ public class MediaRouteChooserDialog extends androidx.appcompat.app.AppCompatDialog {
+ ctor public MediaRouteChooserDialog(android.content.Context!);
+ ctor public MediaRouteChooserDialog(android.content.Context!, int);
+ method public androidx.mediarouter.media.MediaRouteSelector getRouteSelector();
+ method public boolean onFilterRoute(androidx.mediarouter.media.MediaRouter.RouteInfo);
+ method public void onFilterRoutes(java.util.List<androidx.mediarouter.media.MediaRouter.RouteInfo!>);
+ method public void refreshRoutes();
+ method public void setRouteSelector(androidx.mediarouter.media.MediaRouteSelector);
+ }
+
+ public class MediaRouteChooserDialogFragment extends androidx.fragment.app.DialogFragment {
+ ctor public MediaRouteChooserDialogFragment();
+ method public androidx.mediarouter.media.MediaRouteSelector! getRouteSelector();
+ method public androidx.mediarouter.app.MediaRouteChooserDialog! onCreateChooserDialog(android.content.Context!, android.os.Bundle!);
+ method public void setRouteSelector(androidx.mediarouter.media.MediaRouteSelector!);
+ }
+
+ public class MediaRouteControllerDialog extends androidx.appcompat.app.AlertDialog {
+ ctor public MediaRouteControllerDialog(android.content.Context!);
+ ctor public MediaRouteControllerDialog(android.content.Context!, int);
+ method public android.view.View! getMediaControlView();
+ method public android.support.v4.media.session.MediaSessionCompat.Token! getMediaSession();
+ method public androidx.mediarouter.media.MediaRouter.RouteInfo! getRoute();
+ method public boolean isVolumeControlEnabled();
+ method public android.view.View! onCreateMediaControlView(android.os.Bundle!);
+ method public void setVolumeControlEnabled(boolean);
+ }
+
+ public class MediaRouteControllerDialogFragment extends androidx.fragment.app.DialogFragment {
+ ctor public MediaRouteControllerDialogFragment();
+ method public androidx.mediarouter.app.MediaRouteControllerDialog! onCreateControllerDialog(android.content.Context!, android.os.Bundle!);
+ }
+
+ public class MediaRouteDialogFactory {
+ ctor public MediaRouteDialogFactory();
+ method public static androidx.mediarouter.app.MediaRouteDialogFactory getDefault();
+ method public androidx.mediarouter.app.MediaRouteChooserDialogFragment onCreateChooserDialogFragment();
+ method public androidx.mediarouter.app.MediaRouteControllerDialogFragment onCreateControllerDialogFragment();
+ }
+
+ public class MediaRouteDiscoveryFragment extends androidx.fragment.app.Fragment {
+ ctor public MediaRouteDiscoveryFragment();
+ method public androidx.mediarouter.media.MediaRouter! getMediaRouter();
+ method public androidx.mediarouter.media.MediaRouteSelector! getRouteSelector();
+ method public androidx.mediarouter.media.MediaRouter.Callback! onCreateCallback();
+ method public int onPrepareCallbackFlags();
+ method public void setRouteSelector(androidx.mediarouter.media.MediaRouteSelector!);
+ }
+
+}
+
+package androidx.mediarouter.media {
+
+ public final class MediaControlIntent {
+ field public static final String ACTION_END_SESSION = "android.media.intent.action.END_SESSION";
+ field public static final String ACTION_ENQUEUE = "android.media.intent.action.ENQUEUE";
+ field public static final String ACTION_GET_SESSION_STATUS = "android.media.intent.action.GET_SESSION_STATUS";
+ field public static final String ACTION_GET_STATUS = "android.media.intent.action.GET_STATUS";
+ field public static final String ACTION_PAUSE = "android.media.intent.action.PAUSE";
+ field public static final String ACTION_PLAY = "android.media.intent.action.PLAY";
+ field public static final String ACTION_REMOVE = "android.media.intent.action.REMOVE";
+ field public static final String ACTION_RESUME = "android.media.intent.action.RESUME";
+ field public static final String ACTION_SEEK = "android.media.intent.action.SEEK";
+ field public static final String ACTION_SEND_MESSAGE = "android.media.intent.action.SEND_MESSAGE";
+ field public static final String ACTION_START_SESSION = "android.media.intent.action.START_SESSION";
+ field public static final String ACTION_STOP = "android.media.intent.action.STOP";
+ field public static final String CATEGORY_LIVE_AUDIO = "android.media.intent.category.LIVE_AUDIO";
+ field public static final String CATEGORY_LIVE_VIDEO = "android.media.intent.category.LIVE_VIDEO";
+ field public static final String CATEGORY_REMOTE_PLAYBACK = "android.media.intent.category.REMOTE_PLAYBACK";
+ field public static final int ERROR_INVALID_ITEM_ID = 3; // 0x3
+ field public static final int ERROR_INVALID_SESSION_ID = 2; // 0x2
+ field public static final int ERROR_UNKNOWN = 0; // 0x0
+ field public static final int ERROR_UNSUPPORTED_OPERATION = 1; // 0x1
+ field public static final String EXTRA_ERROR_CODE = "android.media.intent.extra.ERROR_CODE";
+ field public static final String EXTRA_ITEM_CONTENT_POSITION = "android.media.intent.extra.ITEM_POSITION";
+ field public static final String EXTRA_ITEM_HTTP_HEADERS = "android.media.intent.extra.HTTP_HEADERS";
+ field public static final String EXTRA_ITEM_ID = "android.media.intent.extra.ITEM_ID";
+ field public static final String EXTRA_ITEM_METADATA = "android.media.intent.extra.ITEM_METADATA";
+ field public static final String EXTRA_ITEM_STATUS = "android.media.intent.extra.ITEM_STATUS";
+ field public static final String EXTRA_ITEM_STATUS_UPDATE_RECEIVER = "android.media.intent.extra.ITEM_STATUS_UPDATE_RECEIVER";
+ field public static final String EXTRA_MESSAGE = "android.media.intent.extra.MESSAGE";
+ field public static final String EXTRA_MESSAGE_RECEIVER = "android.media.intent.extra.MESSAGE_RECEIVER";
+ field public static final String EXTRA_SESSION_ID = "android.media.intent.extra.SESSION_ID";
+ field public static final String EXTRA_SESSION_STATUS = "android.media.intent.extra.SESSION_STATUS";
+ field public static final String EXTRA_SESSION_STATUS_UPDATE_RECEIVER = "android.media.intent.extra.SESSION_STATUS_UPDATE_RECEIVER";
+ }
+
+ public final class MediaItemMetadata {
+ field public static final String KEY_ALBUM_ARTIST = "android.media.metadata.ALBUM_ARTIST";
+ field public static final String KEY_ALBUM_TITLE = "android.media.metadata.ALBUM_TITLE";
+ field public static final String KEY_ARTIST = "android.media.metadata.ARTIST";
+ field public static final String KEY_ARTWORK_URI = "android.media.metadata.ARTWORK_URI";
+ field public static final String KEY_AUTHOR = "android.media.metadata.AUTHOR";
+ field public static final String KEY_COMPOSER = "android.media.metadata.COMPOSER";
+ field public static final String KEY_DISC_NUMBER = "android.media.metadata.DISC_NUMBER";
+ field public static final String KEY_DURATION = "android.media.metadata.DURATION";
+ field public static final String KEY_TITLE = "android.media.metadata.TITLE";
+ field public static final String KEY_TRACK_NUMBER = "android.media.metadata.TRACK_NUMBER";
+ field public static final String KEY_YEAR = "android.media.metadata.YEAR";
+ }
+
+ public final class MediaItemStatus {
+ method public android.os.Bundle! asBundle();
+ method public static androidx.mediarouter.media.MediaItemStatus! fromBundle(android.os.Bundle!);
+ method public long getContentDuration();
+ method public long getContentPosition();
+ method public android.os.Bundle! getExtras();
+ method public int getPlaybackState();
+ method public long getTimestamp();
+ field public static final String EXTRA_HTTP_RESPONSE_HEADERS = "android.media.status.extra.HTTP_RESPONSE_HEADERS";
+ field public static final String EXTRA_HTTP_STATUS_CODE = "android.media.status.extra.HTTP_STATUS_CODE";
+ field public static final int PLAYBACK_STATE_BUFFERING = 3; // 0x3
+ field public static final int PLAYBACK_STATE_CANCELED = 5; // 0x5
+ field public static final int PLAYBACK_STATE_ERROR = 7; // 0x7
+ field public static final int PLAYBACK_STATE_FINISHED = 4; // 0x4
+ field public static final int PLAYBACK_STATE_INVALIDATED = 6; // 0x6
+ field public static final int PLAYBACK_STATE_PAUSED = 2; // 0x2
+ field public static final int PLAYBACK_STATE_PENDING = 0; // 0x0
+ field public static final int PLAYBACK_STATE_PLAYING = 1; // 0x1
+ }
+
+ public static final class MediaItemStatus.Builder {
+ ctor public MediaItemStatus.Builder(int);
+ ctor public MediaItemStatus.Builder(androidx.mediarouter.media.MediaItemStatus!);
+ method public androidx.mediarouter.media.MediaItemStatus! build();
+ method public androidx.mediarouter.media.MediaItemStatus.Builder! setContentDuration(long);
+ method public androidx.mediarouter.media.MediaItemStatus.Builder! setContentPosition(long);
+ method public androidx.mediarouter.media.MediaItemStatus.Builder! setExtras(android.os.Bundle!);
+ method public androidx.mediarouter.media.MediaItemStatus.Builder! setPlaybackState(int);
+ method public androidx.mediarouter.media.MediaItemStatus.Builder! setTimestamp(long);
+ }
+
+ public final class MediaRouteDescriptor {
+ method public android.os.Bundle! asBundle();
+ method public boolean canDisconnectAndKeepPlaying();
+ method public static androidx.mediarouter.media.MediaRouteDescriptor! fromBundle(android.os.Bundle!);
+ method public int getConnectionState();
+ method public java.util.List<android.content.IntentFilter!>! getControlFilters();
+ method public String! getDescription();
+ method public int getDeviceType();
+ method public android.os.Bundle! getExtras();
+ method public android.net.Uri! getIconUri();
+ method public String! getId();
+ method public String! getName();
+ method public int getPlaybackStream();
+ method public int getPlaybackType();
+ method public int getPresentationDisplayId();
+ method public android.content.IntentSender! getSettingsActivity();
+ method public int getVolume();
+ method public int getVolumeHandling();
+ method public int getVolumeMax();
+ method @Deprecated public boolean isConnecting();
+ method public boolean isDynamicGroupRoute();
+ method public boolean isEnabled();
+ method public boolean isValid();
+ }
+
+ public static final class MediaRouteDescriptor.Builder {
+ ctor public MediaRouteDescriptor.Builder(String!, String!);
+ ctor public MediaRouteDescriptor.Builder(androidx.mediarouter.media.MediaRouteDescriptor!);
+ method public androidx.mediarouter.media.MediaRouteDescriptor.Builder! addControlFilter(android.content.IntentFilter!);
+ method public androidx.mediarouter.media.MediaRouteDescriptor.Builder! addControlFilters(java.util.Collection<android.content.IntentFilter!>!);
+ method public androidx.mediarouter.media.MediaRouteDescriptor! build();
+ method public androidx.mediarouter.media.MediaRouteDescriptor.Builder! setCanDisconnect(boolean);
+ method @Deprecated public androidx.mediarouter.media.MediaRouteDescriptor.Builder! setConnecting(boolean);
+ method public androidx.mediarouter.media.MediaRouteDescriptor.Builder! setConnectionState(int);
+ method public androidx.mediarouter.media.MediaRouteDescriptor.Builder! setDescription(String!);
+ method public androidx.mediarouter.media.MediaRouteDescriptor.Builder! setDeviceType(int);
+ method public androidx.mediarouter.media.MediaRouteDescriptor.Builder! setEnabled(boolean);
+ method public androidx.mediarouter.media.MediaRouteDescriptor.Builder! setExtras(android.os.Bundle!);
+ method public androidx.mediarouter.media.MediaRouteDescriptor.Builder! setIconUri(android.net.Uri!);
+ method public androidx.mediarouter.media.MediaRouteDescriptor.Builder! setId(String!);
+ method public androidx.mediarouter.media.MediaRouteDescriptor.Builder! setIsDynamicGroupRoute(boolean);
+ method public androidx.mediarouter.media.MediaRouteDescriptor.Builder! setName(String!);
+ method public androidx.mediarouter.media.MediaRouteDescriptor.Builder! setPlaybackStream(int);
+ method public androidx.mediarouter.media.MediaRouteDescriptor.Builder! setPlaybackType(int);
+ method public androidx.mediarouter.media.MediaRouteDescriptor.Builder! setPresentationDisplayId(int);
+ method public androidx.mediarouter.media.MediaRouteDescriptor.Builder! setSettingsActivity(android.content.IntentSender!);
+ method public androidx.mediarouter.media.MediaRouteDescriptor.Builder! setVolume(int);
+ method public androidx.mediarouter.media.MediaRouteDescriptor.Builder! setVolumeHandling(int);
+ method public androidx.mediarouter.media.MediaRouteDescriptor.Builder! setVolumeMax(int);
+ }
+
+ public final class MediaRouteDiscoveryRequest {
+ ctor public MediaRouteDiscoveryRequest(androidx.mediarouter.media.MediaRouteSelector!, boolean);
+ method public android.os.Bundle! asBundle();
+ method public static androidx.mediarouter.media.MediaRouteDiscoveryRequest! fromBundle(android.os.Bundle!);
+ method public androidx.mediarouter.media.MediaRouteSelector! getSelector();
+ method public boolean isActiveScan();
+ method public boolean isValid();
+ }
+
+ public abstract class MediaRouteProvider {
+ ctor public MediaRouteProvider(android.content.Context);
+ method public final android.content.Context! getContext();
+ method public final androidx.mediarouter.media.MediaRouteProviderDescriptor? getDescriptor();
+ method public final androidx.mediarouter.media.MediaRouteDiscoveryRequest? getDiscoveryRequest();
+ method public final android.os.Handler! getHandler();
+ method public final androidx.mediarouter.media.MediaRouteProvider.ProviderMetadata! getMetadata();
+ method public androidx.mediarouter.media.MediaRouteProvider.DynamicGroupRouteController? onCreateDynamicGroupRouteController(String);
+ method public androidx.mediarouter.media.MediaRouteProvider.RouteController? onCreateRouteController(String);
+ method public void onDiscoveryRequestChanged(androidx.mediarouter.media.MediaRouteDiscoveryRequest?);
+ method public final void setCallback(androidx.mediarouter.media.MediaRouteProvider.Callback?);
+ method public final void setDescriptor(androidx.mediarouter.media.MediaRouteProviderDescriptor?);
+ method public final void setDiscoveryRequest(androidx.mediarouter.media.MediaRouteDiscoveryRequest!);
+ }
+
+ public abstract static class MediaRouteProvider.Callback {
+ ctor public MediaRouteProvider.Callback();
+ method public void onDescriptorChanged(androidx.mediarouter.media.MediaRouteProvider, androidx.mediarouter.media.MediaRouteProviderDescriptor?);
+ }
+
+ public abstract static class MediaRouteProvider.DynamicGroupRouteController extends androidx.mediarouter.media.MediaRouteProvider.RouteController {
+ ctor public MediaRouteProvider.DynamicGroupRouteController();
+ method public String? getGroupableSelectionTitle();
+ method public String? getTransferableSectionTitle();
+ method @Deprecated public final void notifyDynamicRoutesChanged(java.util.Collection<androidx.mediarouter.media.MediaRouteProvider.DynamicGroupRouteController.DynamicRouteDescriptor!>!);
+ method public final void notifyDynamicRoutesChanged(androidx.mediarouter.media.MediaRouteDescriptor, java.util.Collection<androidx.mediarouter.media.MediaRouteProvider.DynamicGroupRouteController.DynamicRouteDescriptor!>);
+ method public abstract void onAddMemberRoute(String);
+ method public abstract void onRemoveMemberRoute(String!);
+ method public abstract void onUpdateMemberRoutes(java.util.List<java.lang.String!>?);
+ }
+
+ public static final class MediaRouteProvider.DynamicGroupRouteController.DynamicRouteDescriptor {
+ method public androidx.mediarouter.media.MediaRouteDescriptor getRouteDescriptor();
+ method public int getSelectionState();
+ method public boolean isGroupable();
+ method public boolean isTransferable();
+ method public boolean isUnselectable();
+ field public static final int SELECTED = 3; // 0x3
+ field public static final int SELECTING = 2; // 0x2
+ field public static final int UNSELECTED = 1; // 0x1
+ field public static final int UNSELECTING = 0; // 0x0
+ }
+
+ public static final class MediaRouteProvider.DynamicGroupRouteController.DynamicRouteDescriptor.Builder {
+ ctor public MediaRouteProvider.DynamicGroupRouteController.DynamicRouteDescriptor.Builder(androidx.mediarouter.media.MediaRouteDescriptor!);
+ ctor public MediaRouteProvider.DynamicGroupRouteController.DynamicRouteDescriptor.Builder(androidx.mediarouter.media.MediaRouteProvider.DynamicGroupRouteController.DynamicRouteDescriptor!);
+ method public androidx.mediarouter.media.MediaRouteProvider.DynamicGroupRouteController.DynamicRouteDescriptor! build();
+ method public androidx.mediarouter.media.MediaRouteProvider.DynamicGroupRouteController.DynamicRouteDescriptor.Builder! setIsGroupable(boolean);
+ method public androidx.mediarouter.media.MediaRouteProvider.DynamicGroupRouteController.DynamicRouteDescriptor.Builder! setIsTransferable(boolean);
+ method public androidx.mediarouter.media.MediaRouteProvider.DynamicGroupRouteController.DynamicRouteDescriptor.Builder! setIsUnselectable(boolean);
+ method public androidx.mediarouter.media.MediaRouteProvider.DynamicGroupRouteController.DynamicRouteDescriptor.Builder! setSelectionState(int);
+ }
+
+ public static final class MediaRouteProvider.ProviderMetadata {
+ method public android.content.ComponentName! getComponentName();
+ method public String! getPackageName();
+ }
+
+ public abstract static class MediaRouteProvider.RouteController {
+ ctor public MediaRouteProvider.RouteController();
+ method public boolean onControlRequest(android.content.Intent!, androidx.mediarouter.media.MediaRouter.ControlRequestCallback?);
+ method public void onRelease();
+ method public void onSelect();
+ method public void onSetVolume(int);
+ method @Deprecated public void onUnselect();
+ method public void onUnselect(int);
+ method public void onUpdateVolume(int);
+ }
+
+ public final class MediaRouteProviderDescriptor {
+ method public android.os.Bundle! asBundle();
+ method public static androidx.mediarouter.media.MediaRouteProviderDescriptor! fromBundle(android.os.Bundle!);
+ method public java.util.List<androidx.mediarouter.media.MediaRouteDescriptor!> getRoutes();
+ method public boolean isValid();
+ method public boolean supportsDynamicGroupRoute();
+ }
+
+ public static final class MediaRouteProviderDescriptor.Builder {
+ ctor public MediaRouteProviderDescriptor.Builder();
+ ctor public MediaRouteProviderDescriptor.Builder(androidx.mediarouter.media.MediaRouteProviderDescriptor!);
+ method public androidx.mediarouter.media.MediaRouteProviderDescriptor.Builder! addRoute(androidx.mediarouter.media.MediaRouteDescriptor!);
+ method public androidx.mediarouter.media.MediaRouteProviderDescriptor.Builder! addRoutes(java.util.Collection<androidx.mediarouter.media.MediaRouteDescriptor!>!);
+ method public androidx.mediarouter.media.MediaRouteProviderDescriptor! build();
+ method public androidx.mediarouter.media.MediaRouteProviderDescriptor.Builder! setSupportsDynamicGroupRoute(boolean);
+ }
+
+ public abstract class MediaRouteProviderService extends android.app.Service {
+ ctor public MediaRouteProviderService();
+ method public androidx.mediarouter.media.MediaRouteProvider! getMediaRouteProvider();
+ method public android.os.IBinder! onBind(android.content.Intent!);
+ method public abstract androidx.mediarouter.media.MediaRouteProvider! onCreateMediaRouteProvider();
+ field public static final String SERVICE_INTERFACE = "android.media.MediaRouteProviderService";
+ }
+
+ public final class MediaRouteSelector {
+ method public android.os.Bundle! asBundle();
+ method public boolean contains(androidx.mediarouter.media.MediaRouteSelector!);
+ method public static androidx.mediarouter.media.MediaRouteSelector! fromBundle(android.os.Bundle?);
+ method public java.util.List<java.lang.String!>! getControlCategories();
+ method public boolean hasControlCategory(String!);
+ method public boolean isEmpty();
+ method public boolean isValid();
+ method public boolean matchesControlFilters(java.util.List<android.content.IntentFilter!>!);
+ field public static final androidx.mediarouter.media.MediaRouteSelector! EMPTY;
+ }
+
+ public static final class MediaRouteSelector.Builder {
+ ctor public MediaRouteSelector.Builder();
+ ctor public MediaRouteSelector.Builder(androidx.mediarouter.media.MediaRouteSelector);
+ method public androidx.mediarouter.media.MediaRouteSelector.Builder addControlCategories(java.util.Collection<java.lang.String!>);
+ method public androidx.mediarouter.media.MediaRouteSelector.Builder addControlCategory(String);
+ method public androidx.mediarouter.media.MediaRouteSelector.Builder addSelector(androidx.mediarouter.media.MediaRouteSelector);
+ method public androidx.mediarouter.media.MediaRouteSelector build();
+ }
+
+ public final class MediaRouter {
+ method public void addCallback(androidx.mediarouter.media.MediaRouteSelector!, androidx.mediarouter.media.MediaRouter.Callback!);
+ method public void addCallback(androidx.mediarouter.media.MediaRouteSelector, androidx.mediarouter.media.MediaRouter.Callback, int);
+ method public void addProvider(androidx.mediarouter.media.MediaRouteProvider);
+ method public void addRemoteControlClient(Object);
+ method public androidx.mediarouter.media.MediaRouter.RouteInfo! getBluetoothRoute();
+ method public androidx.mediarouter.media.MediaRouter.RouteInfo getDefaultRoute();
+ method public static androidx.mediarouter.media.MediaRouter! getInstance(android.content.Context);
+ method public android.support.v4.media.session.MediaSessionCompat.Token! getMediaSessionToken();
+ method public java.util.List<androidx.mediarouter.media.MediaRouter.ProviderInfo!>! getProviders();
+ method public androidx.mediarouter.media.MediaRouterParams? getRouterParams();
+ method public java.util.List<androidx.mediarouter.media.MediaRouter.RouteInfo!>! getRoutes();
+ method public androidx.mediarouter.media.MediaRouter.RouteInfo getSelectedRoute();
+ method public boolean isRouteAvailable(androidx.mediarouter.media.MediaRouteSelector, int);
+ method public void removeCallback(androidx.mediarouter.media.MediaRouter.Callback);
+ method public void removeProvider(androidx.mediarouter.media.MediaRouteProvider);
+ method public void removeRemoteControlClient(Object);
+ method public void selectRoute(androidx.mediarouter.media.MediaRouter.RouteInfo);
+ method public void setMediaSession(Object!);
+ method public void setMediaSessionCompat(android.support.v4.media.session.MediaSessionCompat!);
+ method @MainThread public void setOnPrepareTransferListener(androidx.mediarouter.media.MediaRouter.OnPrepareTransferListener?);
+ method public void setRouterParams(androidx.mediarouter.media.MediaRouterParams?);
+ method public void unselect(int);
+ method public androidx.mediarouter.media.MediaRouter.RouteInfo updateSelectedRoute(androidx.mediarouter.media.MediaRouteSelector);
+ field public static final int AVAILABILITY_FLAG_IGNORE_DEFAULT_ROUTE = 1; // 0x1
+ field public static final int AVAILABILITY_FLAG_REQUIRE_MATCH = 2; // 0x2
+ field public static final int CALLBACK_FLAG_FORCE_DISCOVERY = 8; // 0x8
+ field public static final int CALLBACK_FLAG_PERFORM_ACTIVE_SCAN = 1; // 0x1
+ field public static final int CALLBACK_FLAG_REQUEST_DISCOVERY = 4; // 0x4
+ field public static final int CALLBACK_FLAG_UNFILTERED_EVENTS = 2; // 0x2
+ field public static final int UNSELECT_REASON_DISCONNECTED = 1; // 0x1
+ field public static final int UNSELECT_REASON_ROUTE_CHANGED = 3; // 0x3
+ field public static final int UNSELECT_REASON_STOPPED = 2; // 0x2
+ field public static final int UNSELECT_REASON_UNKNOWN = 0; // 0x0
+ }
+
+ public abstract static class MediaRouter.Callback {
+ ctor public MediaRouter.Callback();
+ method public void onProviderAdded(androidx.mediarouter.media.MediaRouter!, androidx.mediarouter.media.MediaRouter.ProviderInfo!);
+ method public void onProviderChanged(androidx.mediarouter.media.MediaRouter!, androidx.mediarouter.media.MediaRouter.ProviderInfo!);
+ method public void onProviderRemoved(androidx.mediarouter.media.MediaRouter!, androidx.mediarouter.media.MediaRouter.ProviderInfo!);
+ method public void onRouteAdded(androidx.mediarouter.media.MediaRouter!, androidx.mediarouter.media.MediaRouter.RouteInfo!);
+ method public void onRouteChanged(androidx.mediarouter.media.MediaRouter!, androidx.mediarouter.media.MediaRouter.RouteInfo!);
+ method public void onRoutePresentationDisplayChanged(androidx.mediarouter.media.MediaRouter!, androidx.mediarouter.media.MediaRouter.RouteInfo!);
+ method public void onRouteRemoved(androidx.mediarouter.media.MediaRouter!, androidx.mediarouter.media.MediaRouter.RouteInfo!);
+ method @Deprecated public void onRouteSelected(androidx.mediarouter.media.MediaRouter!, androidx.mediarouter.media.MediaRouter.RouteInfo!);
+ method public void onRouteSelected(androidx.mediarouter.media.MediaRouter, androidx.mediarouter.media.MediaRouter.RouteInfo, int);
+ method public void onRouteSelected(androidx.mediarouter.media.MediaRouter, androidx.mediarouter.media.MediaRouter.RouteInfo, int, androidx.mediarouter.media.MediaRouter.RouteInfo);
+ method @Deprecated public void onRouteUnselected(androidx.mediarouter.media.MediaRouter!, androidx.mediarouter.media.MediaRouter.RouteInfo!);
+ method public void onRouteUnselected(androidx.mediarouter.media.MediaRouter!, androidx.mediarouter.media.MediaRouter.RouteInfo!, int);
+ method public void onRouteVolumeChanged(androidx.mediarouter.media.MediaRouter!, androidx.mediarouter.media.MediaRouter.RouteInfo!);
+ }
+
+ public abstract static class MediaRouter.ControlRequestCallback {
+ ctor public MediaRouter.ControlRequestCallback();
+ method public void onError(String!, android.os.Bundle!);
+ method public void onResult(android.os.Bundle!);
+ }
+
+ public static interface MediaRouter.OnPrepareTransferListener {
+ method @MainThread public com.google.common.util.concurrent.ListenableFuture<java.lang.Void!>? onPrepareTransfer(androidx.mediarouter.media.MediaRouter.RouteInfo, androidx.mediarouter.media.MediaRouter.RouteInfo);
+ }
+
+ public static final class MediaRouter.ProviderInfo {
+ method public android.content.ComponentName! getComponentName();
+ method public String! getPackageName();
+ method public androidx.mediarouter.media.MediaRouteProvider! getProviderInstance();
+ method public java.util.List<androidx.mediarouter.media.MediaRouter.RouteInfo!>! getRoutes();
+ }
+
+ public static class MediaRouter.RouteInfo {
+ method public boolean canDisconnect();
+ method public int getConnectionState();
+ method public java.util.List<android.content.IntentFilter!>! getControlFilters();
+ method public String? getDescription();
+ method public int getDeviceType();
+ method public android.os.Bundle? getExtras();
+ method public android.net.Uri! getIconUri();
+ method public String getId();
+ method public String! getName();
+ method public int getPlaybackStream();
+ method public int getPlaybackType();
+ method public android.view.Display? getPresentationDisplay();
+ method public androidx.mediarouter.media.MediaRouter.ProviderInfo! getProvider();
+ method public android.content.IntentSender? getSettingsIntent();
+ method public int getVolume();
+ method public int getVolumeHandling();
+ method public int getVolumeMax();
+ method public boolean isBluetooth();
+ method @Deprecated public boolean isConnecting();
+ method public boolean isDefault();
+ method public boolean isDeviceSpeaker();
+ method public boolean isEnabled();
+ method public boolean isSelected();
+ method public boolean matchesSelector(androidx.mediarouter.media.MediaRouteSelector);
+ method public void requestSetVolume(int);
+ method public void requestUpdateVolume(int);
+ method public void select();
+ method public void sendControlRequest(android.content.Intent, androidx.mediarouter.media.MediaRouter.ControlRequestCallback?);
+ method public boolean supportsControlAction(String, String);
+ method public boolean supportsControlCategory(String);
+ method public boolean supportsControlRequest(android.content.Intent);
+ field public static final int CONNECTION_STATE_CONNECTED = 2; // 0x2
+ field public static final int CONNECTION_STATE_CONNECTING = 1; // 0x1
+ field public static final int CONNECTION_STATE_DISCONNECTED = 0; // 0x0
+ field public static final int DEVICE_TYPE_SPEAKER = 2; // 0x2
+ field public static final int DEVICE_TYPE_TV = 1; // 0x1
+ field public static final int PLAYBACK_TYPE_LOCAL = 0; // 0x0
+ field public static final int PLAYBACK_TYPE_REMOTE = 1; // 0x1
+ field public static final int PLAYBACK_VOLUME_FIXED = 0; // 0x0
+ field public static final int PLAYBACK_VOLUME_VARIABLE = 1; // 0x1
+ }
+
+ public class MediaRouterParams {
+ method public int getDialogType();
+ method public boolean isOutputSwitcherEnabled();
+ method public boolean isTransferToLocalEnabled();
+ field public static final int DIALOG_TYPE_DEFAULT = 1; // 0x1
+ field public static final int DIALOG_TYPE_DYNAMIC_GROUP = 2; // 0x2
+ }
+
+ public static final class MediaRouterParams.Builder {
+ ctor public MediaRouterParams.Builder();
+ ctor public MediaRouterParams.Builder(androidx.mediarouter.media.MediaRouterParams);
+ method public androidx.mediarouter.media.MediaRouterParams build();
+ method public androidx.mediarouter.media.MediaRouterParams.Builder setDialogType(int);
+ method public androidx.mediarouter.media.MediaRouterParams.Builder setOutputSwitcherEnabled(boolean);
+ method public androidx.mediarouter.media.MediaRouterParams.Builder setTransferToLocalEnabled(boolean);
+ }
+
+ public final class MediaSessionStatus {
+ method public android.os.Bundle! asBundle();
+ method public static androidx.mediarouter.media.MediaSessionStatus! fromBundle(android.os.Bundle!);
+ method public android.os.Bundle! getExtras();
+ method public int getSessionState();
+ method public long getTimestamp();
+ method public boolean isQueuePaused();
+ field public static final int SESSION_STATE_ACTIVE = 0; // 0x0
+ field public static final int SESSION_STATE_ENDED = 1; // 0x1
+ field public static final int SESSION_STATE_INVALIDATED = 2; // 0x2
+ }
+
+ public static final class MediaSessionStatus.Builder {
+ ctor public MediaSessionStatus.Builder(int);
+ ctor public MediaSessionStatus.Builder(androidx.mediarouter.media.MediaSessionStatus!);
+ method public androidx.mediarouter.media.MediaSessionStatus! build();
+ method public androidx.mediarouter.media.MediaSessionStatus.Builder! setExtras(android.os.Bundle!);
+ method public androidx.mediarouter.media.MediaSessionStatus.Builder! setQueuePaused(boolean);
+ method public androidx.mediarouter.media.MediaSessionStatus.Builder! setSessionState(int);
+ method public androidx.mediarouter.media.MediaSessionStatus.Builder! setTimestamp(long);
+ }
+
+ public final class MediaTransferReceiver extends android.content.BroadcastReceiver {
+ ctor public MediaTransferReceiver();
+ method public void onReceive(android.content.Context, android.content.Intent);
+ }
+
+ public class RemotePlaybackClient {
+ ctor public RemotePlaybackClient(android.content.Context!, androidx.mediarouter.media.MediaRouter.RouteInfo!);
+ method public void endSession(android.os.Bundle!, androidx.mediarouter.media.RemotePlaybackClient.SessionActionCallback!);
+ method public void enqueue(android.net.Uri!, String!, android.os.Bundle!, long, android.os.Bundle!, androidx.mediarouter.media.RemotePlaybackClient.ItemActionCallback!);
+ method public String! getSessionId();
+ method public void getSessionStatus(android.os.Bundle!, androidx.mediarouter.media.RemotePlaybackClient.SessionActionCallback!);
+ method public void getStatus(String!, android.os.Bundle!, androidx.mediarouter.media.RemotePlaybackClient.ItemActionCallback!);
+ method public boolean hasSession();
+ method public boolean isMessagingSupported();
+ method public boolean isQueuingSupported();
+ method public boolean isRemotePlaybackSupported();
+ method public boolean isSessionManagementSupported();
+ method public void pause(android.os.Bundle!, androidx.mediarouter.media.RemotePlaybackClient.SessionActionCallback!);
+ method public void play(android.net.Uri!, String!, android.os.Bundle!, long, android.os.Bundle!, androidx.mediarouter.media.RemotePlaybackClient.ItemActionCallback!);
+ method public void release();
+ method public void remove(String!, android.os.Bundle!, androidx.mediarouter.media.RemotePlaybackClient.ItemActionCallback!);
+ method public void resume(android.os.Bundle!, androidx.mediarouter.media.RemotePlaybackClient.SessionActionCallback!);
+ method public void seek(String!, long, android.os.Bundle!, androidx.mediarouter.media.RemotePlaybackClient.ItemActionCallback!);
+ method public void sendMessage(android.os.Bundle!, androidx.mediarouter.media.RemotePlaybackClient.SessionActionCallback!);
+ method public void setOnMessageReceivedListener(androidx.mediarouter.media.RemotePlaybackClient.OnMessageReceivedListener!);
+ method public void setSessionId(String!);
+ method public void setStatusCallback(androidx.mediarouter.media.RemotePlaybackClient.StatusCallback!);
+ method public void startSession(android.os.Bundle!, androidx.mediarouter.media.RemotePlaybackClient.SessionActionCallback!);
+ method public void stop(android.os.Bundle!, androidx.mediarouter.media.RemotePlaybackClient.SessionActionCallback!);
+ }
+
+ public abstract static class RemotePlaybackClient.ActionCallback {
+ ctor public RemotePlaybackClient.ActionCallback();
+ method public void onError(String!, int, android.os.Bundle!);
+ }
+
+ public abstract static class RemotePlaybackClient.ItemActionCallback extends androidx.mediarouter.media.RemotePlaybackClient.ActionCallback {
+ ctor public RemotePlaybackClient.ItemActionCallback();
+ method public void onResult(android.os.Bundle!, String!, androidx.mediarouter.media.MediaSessionStatus!, String!, androidx.mediarouter.media.MediaItemStatus!);
+ }
+
+ public static interface RemotePlaybackClient.OnMessageReceivedListener {
+ method public void onMessageReceived(String!, android.os.Bundle!);
+ }
+
+ public abstract static class RemotePlaybackClient.SessionActionCallback extends androidx.mediarouter.media.RemotePlaybackClient.ActionCallback {
+ ctor public RemotePlaybackClient.SessionActionCallback();
+ method public void onResult(android.os.Bundle!, String!, androidx.mediarouter.media.MediaSessionStatus!);
+ }
+
+ public abstract static class RemotePlaybackClient.StatusCallback {
+ ctor public RemotePlaybackClient.StatusCallback();
+ method public void onItemStatusChanged(android.os.Bundle!, String!, androidx.mediarouter.media.MediaSessionStatus!, String!, androidx.mediarouter.media.MediaItemStatus!);
+ method public void onSessionChanged(String!);
+ method public void onSessionStatusChanged(android.os.Bundle!, String!, androidx.mediarouter.media.MediaSessionStatus!);
+ }
+
+}
+
diff --git a/mediarouter/mediarouter/build.gradle b/mediarouter/mediarouter/build.gradle
index e219716..dce6363 100644
--- a/mediarouter/mediarouter/build.gradle
+++ b/mediarouter/mediarouter/build.gradle
@@ -24,7 +24,7 @@
}
dependencies {
- api("androidx.media:media:1.2.0-alpha04")
+ api("androidx.media:media:1.2.0-beta01")
api(GUAVA_LISTENABLE_FUTURE)
implementation("androidx.appcompat:appcompat:1.1.0")
diff --git a/mediarouter/mediarouter/src/main/java/androidx/mediarouter/media/MediaRoute2Provider.java b/mediarouter/mediarouter/src/main/java/androidx/mediarouter/media/MediaRoute2Provider.java
index ee27e58..a65e7a2 100644
--- a/mediarouter/mediarouter/src/main/java/androidx/mediarouter/media/MediaRoute2Provider.java
+++ b/mediarouter/mediarouter/src/main/java/androidx/mediarouter/media/MediaRoute2Provider.java
@@ -436,6 +436,9 @@
}
private class GroupRouteController extends DynamicGroupRouteController {
+ // Time to clear mOptimisticVolume
+ private static final long OPTIMISTIC_VOLUME_TIMEOUT_MS = 1_000;
+
final String mInitialMemberRouteId;
final MediaRouter2.RoutingController mRoutingController;
@Nullable
@@ -443,9 +446,13 @@
@Nullable
final Messenger mReceiveMessenger;
final SparseArray<ControlRequestCallback> mPendingCallbacks = new SparseArray<>();
-
+ final Handler mControllerHandler;
AtomicInteger mNextRequestId = new AtomicInteger(1);
+ private final Runnable mClearOptimisticVolumeRunnable = () -> mOptimisticVolume = -1;
+ // The possible current volume set by the user recently or -1 if not.
+ int mOptimisticVolume = -1;
+
GroupRouteController(@NonNull MediaRouter2.RoutingController routingController,
@NonNull String initialMemberRouteId) {
mRoutingController = routingController;
@@ -453,6 +460,7 @@
mServiceMessenger = getMessengerFromRoutingController(routingController);
mReceiveMessenger = mServiceMessenger == null ? null :
new Messenger(new ReceiveHandler());
+ mControllerHandler = new Handler(Looper.getMainLooper());
}
@Override
@@ -461,6 +469,8 @@
return;
}
mRoutingController.setVolume(volume);
+ mOptimisticVolume = volume;
+ scheduleClearOptimisticVolume();
}
@Override
@@ -468,7 +478,12 @@
if (mRoutingController == null) {
return;
}
- mRoutingController.setVolume(mRoutingController.getVolume() + delta);
+ int volumeBefore = mOptimisticVolume < 0 ? mRoutingController.getVolume() :
+ mOptimisticVolume;
+ mOptimisticVolume = Math.max(0, Math.min(volumeBefore + delta,
+ mRoutingController.getVolumeMax()));
+ mRoutingController.setVolume(mOptimisticVolume);
+ scheduleClearOptimisticVolume();
}
@Override
@@ -554,6 +569,12 @@
mRoutingController.deselectRoute(route);
}
+ private void scheduleClearOptimisticVolume() {
+ mControllerHandler.removeCallbacks(mClearOptimisticVolumeRunnable);
+ mControllerHandler.postDelayed(mClearOptimisticVolumeRunnable,
+ OPTIMISTIC_VOLUME_TIMEOUT_MS);
+ }
+
void setMemberRouteVolume(@NonNull String memberRouteOriginalId, int volume) {
int requestId = mNextRequestId.getAndIncrement();
Message msg = Message.obtain();
diff --git a/mediarouter/mediarouter/src/main/java/androidx/mediarouter/media/MediaRouteProvider.java b/mediarouter/mediarouter/src/main/java/androidx/mediarouter/media/MediaRouteProvider.java
index 4c59b4d..553c3b1 100644
--- a/mediarouter/mediarouter/src/main/java/androidx/mediarouter/media/MediaRouteProvider.java
+++ b/mediarouter/mediarouter/src/main/java/androidx/mediarouter/media/MediaRouteProvider.java
@@ -587,9 +587,15 @@
* dynamic group state.
* </p>
* @param groupRoute The media route descriptor describing the dynamic group.
- * The name, description, and volume information are used.
+ * The {@link MediaRouter#getSelectedRoute() selected route} of the
+ * media router will contain this information.
+ * If it is {@link MediaRouteDescriptor#isEnabled() disabled},
+ * the media router will unselect the dynamic group and release
+ * the route controller.
* @param dynamicRoutes The dynamic route descriptors for published routes.
* At least a selected or selecting route must be included.
+ * @throws IllegalArgumentException if {@code dynamicRoutes} doesn't contain a selected
+ * or selecting route.
*/
public final void notifyDynamicRoutesChanged(
@NonNull MediaRouteDescriptor groupRoute,
@@ -600,6 +606,18 @@
if (dynamicRoutes == null) {
throw new NullPointerException("dynamicRoutes must not be null");
}
+ boolean hasSelectedRoute = false;
+ for (DynamicRouteDescriptor descriptor : dynamicRoutes) {
+ if (descriptor.mSelectionState == DynamicRouteDescriptor.SELECTING
+ || descriptor.mSelectionState == DynamicRouteDescriptor.SELECTED) {
+ hasSelectedRoute = true;
+ break;
+ }
+ }
+ if (!hasSelectedRoute) {
+ throw new IllegalArgumentException("dynamicRoutes must have a selected or "
+ + "selecting route.");
+ }
synchronized (mLock) {
if (mExecutor != null) {
final OnDynamicRoutesChangedListener listener = mListener;
diff --git a/paging/common/src/main/kotlin/androidx/paging/PageFetcherSnapshot.kt b/paging/common/src/main/kotlin/androidx/paging/PageFetcherSnapshot.kt
index df615df..f00373d 100644
--- a/paging/common/src/main/kotlin/androidx/paging/PageFetcherSnapshot.kt
+++ b/paging/common/src/main/kotlin/androidx/paging/PageFetcherSnapshot.kt
@@ -701,15 +701,4 @@
* Generation of cancel token not [PageFetcherSnapshot]. [generationId] is used to differentiate
* between loads from jobs that have been cancelled, but continued to run to completion.
*/
-private data class GenerationalViewportHint(val generationId: Int, val hint: ViewportHint) {
- companion object {
- val PREPEND_INITIAL_VALUE = GenerationalViewportHint(
- 0,
- ViewportHint(Int.MAX_VALUE, Int.MAX_VALUE, 0, 0, 0, 0)
- )
- val APPEND_INITIAL_VALUE = GenerationalViewportHint(
- 0,
- ViewportHint(Int.MIN_VALUE, Int.MIN_VALUE, 0, 0, 0, 0)
- )
- }
-}
+private data class GenerationalViewportHint(val generationId: Int, val hint: ViewportHint)
diff --git a/paging/common/src/main/kotlin/androidx/paging/ViewportHint.kt b/paging/common/src/main/kotlin/androidx/paging/ViewportHint.kt
index 3b183cf..e11089d 100644
--- a/paging/common/src/main/kotlin/androidx/paging/ViewportHint.kt
+++ b/paging/common/src/main/kotlin/androidx/paging/ViewportHint.kt
@@ -16,6 +16,8 @@
package androidx.paging
+import androidx.paging.PagingSource.LoadResult.Page
+
/**
* Load access information blob, containing information from presenter.
*/
@@ -23,11 +25,13 @@
/** Page index offset from initial load */
val pageOffset: Int,
/**
- * Distance from hint to first loaded item: `anchorPosition - firstLoadedItemPosition`
+ * Original index of item in the [Page] with [pageOffset].
*
- * Zero indicates access at boundary
- * Positive -> Within loaded range or in placeholders if greater than size of last page.
- * Negative -> placeholder access.
+ * Three cases to consider:
+ * - [indexInPage] in Page.data.indices -> Hint references original item directly
+ * - [indexInPage] > Page.data.indices -> Hint references a placeholder after the last
+ * presented item.
+ * - [indexInPage] < 0 -> Hint references a placeholder before the first presented item.
*/
val indexInPage: Int,
/**
@@ -39,7 +43,7 @@
*/
val presentedItemsBefore: Int,
/**
- * Distance from hint to first presented item: `anchorPosition - firstLoadedItemPosition`
+ * Distance from hint to last presented item: `size - index - placeholdersAfter - 1`
*
* Zero indicates access at boundary
* Positive -> Within loaded range or in placeholders if greater than size of last page.
diff --git a/preference/preference/api/api_lint.ignore b/preference/preference/api/api_lint.ignore
index 49deee8..baa6674 100644
--- a/preference/preference/api/api_lint.ignore
+++ b/preference/preference/api/api_lint.ignore
@@ -19,6 +19,20 @@
Method parameter should be Collection<CharSequence> (or subclass) instead of raw array; was `java.lang.CharSequence[]`
+CallbackMethodName: androidx.preference.PreferenceGroup.PreferencePositionCallback#getPreferenceAdapterPosition(String):
+ Callback method names must follow the on<Something> style: getPreferenceAdapterPosition
+CallbackMethodName: androidx.preference.PreferenceGroup.PreferencePositionCallback#getPreferenceAdapterPosition(androidx.preference.Preference):
+ Callback method names must follow the on<Something> style: getPreferenceAdapterPosition
+CallbackMethodName: androidx.preference.PreferenceManager.PreferenceComparisonCallback#arePreferenceContentsTheSame(androidx.preference.Preference, androidx.preference.Preference):
+ Callback method names must follow the on<Something> style: arePreferenceContentsTheSame
+CallbackMethodName: androidx.preference.PreferenceManager.PreferenceComparisonCallback#arePreferenceItemsTheSame(androidx.preference.Preference, androidx.preference.Preference):
+ Callback method names must follow the on<Something> style: arePreferenceItemsTheSame
+CallbackMethodName: androidx.preference.PreferenceManager.SimplePreferenceComparisonCallback#arePreferenceContentsTheSame(androidx.preference.Preference, androidx.preference.Preference):
+ Callback method names must follow the on<Something> style: arePreferenceContentsTheSame
+CallbackMethodName: androidx.preference.PreferenceManager.SimplePreferenceComparisonCallback#arePreferenceItemsTheSame(androidx.preference.Preference, androidx.preference.Preference):
+ Callback method names must follow the on<Something> style: arePreferenceItemsTheSame
+
+
ExecutorRegistration: androidx.preference.EditTextPreference#setOnBindEditTextListener(androidx.preference.EditTextPreference.OnBindEditTextListener):
Registration methods should have overload that accepts delivery Executor: `setOnBindEditTextListener`
ExecutorRegistration: androidx.preference.Preference#setOnPreferenceChangeListener(androidx.preference.Preference.OnPreferenceChangeListener):
diff --git a/recyclerview/recyclerview/api/api_lint.ignore b/recyclerview/recyclerview/api/api_lint.ignore
index bd0ba08..a4dc57e 100644
--- a/recyclerview/recyclerview/api/api_lint.ignore
+++ b/recyclerview/recyclerview/api/api_lint.ignore
@@ -23,6 +23,94 @@
Methods must not mention RuntimeException subclasses in throws clauses (was `java.lang.IndexOutOfBoundsException`)
+CallbackMethodName: androidx.recyclerview.widget.AsyncListUtil.DataCallback#fillData(T[], int, int):
+ Callback method names must follow the on<Something> style: fillData
+CallbackMethodName: androidx.recyclerview.widget.AsyncListUtil.DataCallback#getMaxCachedTiles():
+ Callback method names must follow the on<Something> style: getMaxCachedTiles
+CallbackMethodName: androidx.recyclerview.widget.AsyncListUtil.DataCallback#recycleData(T[], int):
+ Callback method names must follow the on<Something> style: recycleData
+CallbackMethodName: androidx.recyclerview.widget.AsyncListUtil.DataCallback#refreshData():
+ Callback method names must follow the on<Something> style: refreshData
+CallbackMethodName: androidx.recyclerview.widget.AsyncListUtil.ViewCallback#extendRangeInto(int[], int[], int):
+ Callback method names must follow the on<Something> style: extendRangeInto
+CallbackMethodName: androidx.recyclerview.widget.AsyncListUtil.ViewCallback#getItemRangeInto(int[]):
+ Callback method names must follow the on<Something> style: getItemRangeInto
+CallbackMethodName: androidx.recyclerview.widget.BatchingListUpdateCallback#dispatchLastEvent():
+ Callback method names must follow the on<Something> style: dispatchLastEvent
+CallbackMethodName: androidx.recyclerview.widget.DiffUtil.Callback#areContentsTheSame(int, int):
+ Callback method names must follow the on<Something> style: areContentsTheSame
+CallbackMethodName: androidx.recyclerview.widget.DiffUtil.Callback#areItemsTheSame(int, int):
+ Callback method names must follow the on<Something> style: areItemsTheSame
+CallbackMethodName: androidx.recyclerview.widget.DiffUtil.Callback#getChangePayload(int, int):
+ Callback method names must follow the on<Something> style: getChangePayload
+CallbackMethodName: androidx.recyclerview.widget.DiffUtil.Callback#getNewListSize():
+ Callback method names must follow the on<Something> style: getNewListSize
+CallbackMethodName: androidx.recyclerview.widget.DiffUtil.Callback#getOldListSize():
+ Callback method names must follow the on<Something> style: getOldListSize
+CallbackMethodName: androidx.recyclerview.widget.DiffUtil.ItemCallback#areContentsTheSame(T, T):
+ Callback method names must follow the on<Something> style: areContentsTheSame
+CallbackMethodName: androidx.recyclerview.widget.DiffUtil.ItemCallback#areItemsTheSame(T, T):
+ Callback method names must follow the on<Something> style: areItemsTheSame
+CallbackMethodName: androidx.recyclerview.widget.DiffUtil.ItemCallback#getChangePayload(T, T):
+ Callback method names must follow the on<Something> style: getChangePayload
+CallbackMethodName: androidx.recyclerview.widget.ItemTouchHelper.Callback#canDropOver(androidx.recyclerview.widget.RecyclerView, androidx.recyclerview.widget.RecyclerView.ViewHolder, androidx.recyclerview.widget.RecyclerView.ViewHolder):
+ Callback method names must follow the on<Something> style: canDropOver
+CallbackMethodName: androidx.recyclerview.widget.ItemTouchHelper.Callback#chooseDropTarget(androidx.recyclerview.widget.RecyclerView.ViewHolder, java.util.List<androidx.recyclerview.widget.RecyclerView.ViewHolder>, int, int):
+ Callback method names must follow the on<Something> style: chooseDropTarget
+CallbackMethodName: androidx.recyclerview.widget.ItemTouchHelper.Callback#clearView(androidx.recyclerview.widget.RecyclerView, androidx.recyclerview.widget.RecyclerView.ViewHolder):
+ Callback method names must follow the on<Something> style: clearView
+CallbackMethodName: androidx.recyclerview.widget.ItemTouchHelper.Callback#convertToAbsoluteDirection(int, int):
+ Callback method names must follow the on<Something> style: convertToAbsoluteDirection
+CallbackMethodName: androidx.recyclerview.widget.ItemTouchHelper.Callback#getAnimationDuration(androidx.recyclerview.widget.RecyclerView, int, float, float):
+ Callback method names must follow the on<Something> style: getAnimationDuration
+CallbackMethodName: androidx.recyclerview.widget.ItemTouchHelper.Callback#getBoundingBoxMargin():
+ Callback method names must follow the on<Something> style: getBoundingBoxMargin
+CallbackMethodName: androidx.recyclerview.widget.ItemTouchHelper.Callback#getMoveThreshold(androidx.recyclerview.widget.RecyclerView.ViewHolder):
+ Callback method names must follow the on<Something> style: getMoveThreshold
+CallbackMethodName: androidx.recyclerview.widget.ItemTouchHelper.Callback#getMovementFlags(androidx.recyclerview.widget.RecyclerView, androidx.recyclerview.widget.RecyclerView.ViewHolder):
+ Callback method names must follow the on<Something> style: getMovementFlags
+CallbackMethodName: androidx.recyclerview.widget.ItemTouchHelper.Callback#getSwipeEscapeVelocity(float):
+ Callback method names must follow the on<Something> style: getSwipeEscapeVelocity
+CallbackMethodName: androidx.recyclerview.widget.ItemTouchHelper.Callback#getSwipeThreshold(androidx.recyclerview.widget.RecyclerView.ViewHolder):
+ Callback method names must follow the on<Something> style: getSwipeThreshold
+CallbackMethodName: androidx.recyclerview.widget.ItemTouchHelper.Callback#getSwipeVelocityThreshold(float):
+ Callback method names must follow the on<Something> style: getSwipeVelocityThreshold
+CallbackMethodName: androidx.recyclerview.widget.ItemTouchHelper.Callback#interpolateOutOfBoundsScroll(androidx.recyclerview.widget.RecyclerView, int, int, int, long):
+ Callback method names must follow the on<Something> style: interpolateOutOfBoundsScroll
+CallbackMethodName: androidx.recyclerview.widget.ItemTouchHelper.Callback#isItemViewSwipeEnabled():
+ Callback method names must follow the on<Something> style: isItemViewSwipeEnabled
+CallbackMethodName: androidx.recyclerview.widget.ItemTouchHelper.Callback#isLongPressDragEnabled():
+ Callback method names must follow the on<Something> style: isLongPressDragEnabled
+CallbackMethodName: androidx.recyclerview.widget.ItemTouchHelper.SimpleCallback#getDragDirs(androidx.recyclerview.widget.RecyclerView, androidx.recyclerview.widget.RecyclerView.ViewHolder):
+ Callback method names must follow the on<Something> style: getDragDirs
+CallbackMethodName: androidx.recyclerview.widget.ItemTouchHelper.SimpleCallback#getMovementFlags(androidx.recyclerview.widget.RecyclerView, androidx.recyclerview.widget.RecyclerView.ViewHolder):
+ Callback method names must follow the on<Something> style: getMovementFlags
+CallbackMethodName: androidx.recyclerview.widget.ItemTouchHelper.SimpleCallback#getSwipeDirs(androidx.recyclerview.widget.RecyclerView, androidx.recyclerview.widget.RecyclerView.ViewHolder):
+ Callback method names must follow the on<Something> style: getSwipeDirs
+CallbackMethodName: androidx.recyclerview.widget.ItemTouchHelper.SimpleCallback#setDefaultDragDirs(int):
+ Callback method names must follow the on<Something> style: setDefaultDragDirs
+CallbackMethodName: androidx.recyclerview.widget.ItemTouchHelper.SimpleCallback#setDefaultSwipeDirs(int):
+ Callback method names must follow the on<Something> style: setDefaultSwipeDirs
+CallbackMethodName: androidx.recyclerview.widget.SortedList.BatchedCallback#areContentsTheSame(T2, T2):
+ Callback method names must follow the on<Something> style: areContentsTheSame
+CallbackMethodName: androidx.recyclerview.widget.SortedList.BatchedCallback#areItemsTheSame(T2, T2):
+ Callback method names must follow the on<Something> style: areItemsTheSame
+CallbackMethodName: androidx.recyclerview.widget.SortedList.BatchedCallback#compare(T2, T2):
+ Callback method names must follow the on<Something> style: compare
+CallbackMethodName: androidx.recyclerview.widget.SortedList.BatchedCallback#dispatchLastEvent():
+ Callback method names must follow the on<Something> style: dispatchLastEvent
+CallbackMethodName: androidx.recyclerview.widget.SortedList.BatchedCallback#getChangePayload(T2, T2):
+ Callback method names must follow the on<Something> style: getChangePayload
+CallbackMethodName: androidx.recyclerview.widget.SortedList.Callback#areContentsTheSame(T2, T2):
+ Callback method names must follow the on<Something> style: areContentsTheSame
+CallbackMethodName: androidx.recyclerview.widget.SortedList.Callback#areItemsTheSame(T2, T2):
+ Callback method names must follow the on<Something> style: areItemsTheSame
+CallbackMethodName: androidx.recyclerview.widget.SortedList.Callback#compare(T2, T2):
+ Callback method names must follow the on<Something> style: compare
+CallbackMethodName: androidx.recyclerview.widget.SortedList.Callback#getChangePayload(T2, T2):
+ Callback method names must follow the on<Something> style: getChangePayload
+
+
CallbackName: androidx.recyclerview.widget.RecyclerView.AdapterDataObserver:
Class should be named AdapterDataCallback
diff --git a/recyclerview/recyclerview/src/androidTest/java/androidx/recyclerview/widget/RecyclerViewLayoutTest.java b/recyclerview/recyclerview/src/androidTest/java/androidx/recyclerview/widget/RecyclerViewLayoutTest.java
index ab2e48e..228fa79 100644
--- a/recyclerview/recyclerview/src/androidTest/java/androidx/recyclerview/widget/RecyclerViewLayoutTest.java
+++ b/recyclerview/recyclerview/src/androidTest/java/androidx/recyclerview/widget/RecyclerViewLayoutTest.java
@@ -2707,11 +2707,14 @@
// Arrange
+ TestLayoutManager layoutManager = new SimpleTestLayoutManager();
+
RecyclerView recyclerView = new RecyclerView(getActivity());
recyclerView.setAdapter(new TestAdapter(1000));
- recyclerView.setLayoutManager(new TestLayoutManager());
+ recyclerView.setLayoutManager(layoutManager);
+ layoutManager.expectLayouts(1);
setRecyclerView(recyclerView);
- getInstrumentation().waitForIdleSync();
+ layoutManager.waitForLayout(2);
final int[] onScrolledCallCount = new int[1];
final int[] onScrolledTotalScrolled = new int[1];
@@ -2749,7 +2752,8 @@
assertEquals(SCROLL_STATE_SETTLING, (int) onScrollStateChangedStates.get(0));
}
});
- latch.await(5, TimeUnit.SECONDS);
+ assertTrue("smoothScrollBy did not complete within 5 seconds",
+ latch.await(5, TimeUnit.SECONDS));
// Assert that we did indeed finish
assertEquals(100, onScrolledTotalScrolled[0]);
diff --git a/remotecallback/remotecallback/api/api_lint.ignore b/remotecallback/remotecallback/api/api_lint.ignore
index 277ff92..cd4c120 100644
--- a/remotecallback/remotecallback/api/api_lint.ignore
+++ b/remotecallback/remotecallback/api/api_lint.ignore
@@ -1,4 +1,16 @@
// Baseline format: 1.0
+CallbackMethodName: androidx.remotecallback.RemoteCallback#getArgumentBundle():
+ Callback method names must follow the on<Something> style: getArgumentBundle
+CallbackMethodName: androidx.remotecallback.RemoteCallback#getMethodName():
+ Callback method names must follow the on<Something> style: getMethodName
+CallbackMethodName: androidx.remotecallback.RemoteCallback#getReceiverClass():
+ Callback method names must follow the on<Something> style: getReceiverClass
+CallbackMethodName: androidx.remotecallback.RemoteCallback#getType():
+ Callback method names must follow the on<Something> style: getType
+CallbackMethodName: androidx.remotecallback.RemoteCallback#toPendingIntent():
+ Callback method names must follow the on<Something> style: toPendingIntent
+
+
ContextFirst: androidx.remotecallback.RemoteCallback#create(Class<T>, android.content.Context) parameter #1:
Context is distinct, so it must be the first argument (method `create`)
diff --git a/room/compiler-processing/src/main/java/androidx/room/compiler/processing/XType.kt b/room/compiler-processing/src/main/java/androidx/room/compiler/processing/XType.kt
index ee6c3b4e..6dba77b 100644
--- a/room/compiler-processing/src/main/java/androidx/room/compiler/processing/XType.kt
+++ b/room/compiler-processing/src/main/java/androidx/room/compiler/processing/XType.kt
@@ -62,11 +62,6 @@
// TODO these is<Type> checks may need to be moved into the implementation.
// It is not yet clear how we will model some types in Kotlin (e.g. primitives)
/**
- * Returns `true` if this is not `byte` type.
- */
- fun isNotByte() = !isByte()
-
- /**
* Returns `true` if this is an error type.
*/
fun isError(): Boolean
@@ -80,8 +75,6 @@
/**
* Returns boxed version of this type if it is a primitive or itself if it is not a primitive
* type.
- *
- * @see isPrimitive
*/
fun boxed(): XType
@@ -91,36 +84,14 @@
fun asArray(): XArrayType = this as XArrayType
/**
- * Returns `true` if this is an `int`
- */
- fun isPrimitiveInt(): Boolean {
- return typeName == TypeName.INT
- }
-
- /**
- * Returns `true` if this type represents a boxed int (java.lang.Integer)
- */
- fun isBoxedInt() = typeName == TypeName.INT.box()
-
- /**
* Returns `true` if this is a primitive or boxed it
*/
- fun isInt() = isPrimitiveInt() || isBoxedInt()
-
- /**
- * Returns `true` if this is `long`
- */
- fun isPrimitiveLong() = typeName == TypeName.LONG
-
- /**
- * Returns `true` if this is a boxed long (java.lang.Long)
- */
- fun isBoxedLong() = typeName == TypeName.LONG.box()
+ fun isInt(): Boolean
/**
* Returns `true` if this is a primitive or boxed long
*/
- fun isLong() = isPrimitiveLong() || isBoxedLong()
+ fun isLong(): Boolean
/**
* Returns `true` if this is a [List]
@@ -138,29 +109,14 @@
fun isVoidObject(): Boolean = isType() && isTypeOf(Void::class)
/**
- * Returns `true` if this represents a primitive type
- */
- fun isPrimitive() = typeName.isPrimitive
-
- /**
* Returns `true` if this is the kotlin [Unit] type.
*/
fun isKotlinUnit(): Boolean = isType() && isTypeOf(Unit::class)
/**
- * Returns `true` if this is not `void`.
- */
- fun isNotVoid() = !isVoid()
-
- /**
- * Returns `true` if this type represents a valid resolvable type.
- */
- fun isNotError() = !isError()
-
- /**
* Returns `true` if this represents a `byte`.
*/
- fun isByte() = typeName == TypeName.BYTE
+ fun isByte(): Boolean
/**
* Returns `true` if this is the None type.
@@ -168,11 +124,6 @@
fun isNone(): Boolean
/**
- * Returns `true` if this is not the None type.
- */
- fun isNotNone() = !isNone()
-
- /**
* Returns true if this represented by a [XTypeElement].
*/
fun isType(): Boolean
diff --git a/room/compiler-processing/src/main/java/androidx/room/compiler/processing/javac/JavacType.kt b/room/compiler-processing/src/main/java/androidx/room/compiler/processing/javac/JavacType.kt
index 46202ec..3248f58 100644
--- a/room/compiler-processing/src/main/java/androidx/room/compiler/processing/javac/JavacType.kt
+++ b/room/compiler-processing/src/main/java/androidx/room/compiler/processing/javac/JavacType.kt
@@ -23,6 +23,7 @@
import androidx.room.compiler.processing.javac.kotlin.KmType
import androidx.room.compiler.processing.safeTypeName
import com.google.auto.common.MoreTypes
+import com.squareup.javapoet.TypeName
import javax.lang.model.type.TypeKind
import javax.lang.model.type.TypeMirror
import kotlin.reflect.KClass
@@ -36,6 +37,18 @@
override fun isError() = typeMirror.kind == TypeKind.ERROR
+ override fun isInt(): Boolean {
+ return typeName == TypeName.INT || typeName == BOXED_INT
+ }
+
+ override fun isLong(): Boolean {
+ return typeName == TypeName.LONG || typeName == BOXED_LONG
+ }
+
+ override fun isByte(): Boolean {
+ return typeName == TypeName.BYTE || typeName == BOXED_BYTE
+ }
+
override val typeName by lazy {
typeMirror.safeTypeName()
}
@@ -122,4 +135,10 @@
override fun isType(): Boolean {
return MoreTypes.isType(typeMirror)
}
+
+ companion object {
+ private val BOXED_INT = TypeName.INT.box()
+ private val BOXED_LONG = TypeName.LONG.box()
+ private val BOXED_BYTE = TypeName.BYTE.box()
+ }
}
diff --git a/room/compiler-processing/src/test/java/androidx/room/compiler/processing/XExecutableElementTest.kt b/room/compiler-processing/src/test/java/androidx/room/compiler/processing/XExecutableElementTest.kt
index 8eba815..84a40cb 100644
--- a/room/compiler-processing/src/test/java/androidx/room/compiler/processing/XExecutableElementTest.kt
+++ b/room/compiler-processing/src/test/java/androidx/room/compiler/processing/XExecutableElementTest.kt
@@ -22,6 +22,7 @@
import androidx.room.compiler.processing.util.getParameter
import androidx.room.compiler.processing.util.runProcessorTest
import com.google.common.truth.Truth.assertThat
+import com.squareup.javapoet.ClassName
import com.squareup.javapoet.TypeName
import org.junit.Test
import org.junit.runner.RunWith
@@ -38,8 +39,8 @@
package foo.bar;
public class Baz {
private void foo() {}
- public int bar(int param1) {
- return 3;
+ public String bar(String[] param1) {
+ return "";
}
}
""".trimIndent()
@@ -59,10 +60,13 @@
element.getDeclaredMethod("bar").let { method ->
assertThat(method.isOverrideableIgnoringContainer()).isTrue()
assertThat(method.parameters).hasSize(1)
+ val stringTypeName = ClassName.get("java.lang", "String")
method.getParameter("param1").let { param ->
- assertThat(param.type.isPrimitiveInt()).isTrue()
+ assertThat(param.type.isArray()).isTrue()
+ assertThat(param.type.asArray().componentType.typeName)
+ .isEqualTo(stringTypeName)
}
- assertThat(method.returnType.isPrimitiveInt()).isTrue()
+ assertThat(method.returnType.typeName).isEqualTo(stringTypeName)
}
}
}
diff --git a/room/compiler/src/main/kotlin/androidx/room/ext/string_ext.kt b/room/compiler/src/main/kotlin/androidx/room/ext/string_ext.kt
index a91bbd0..2d537f0 100644
--- a/room/compiler/src/main/kotlin/androidx/room/ext/string_ext.kt
+++ b/room/compiler/src/main/kotlin/androidx/room/ext/string_ext.kt
@@ -1,3 +1,5 @@
+import java.util.Locale
+
/*
* Copyright (C) 2016 The Android Open Source Project
*
@@ -17,7 +19,7 @@
private fun String.toCamelCase(): String {
val split = this.split("_")
if (split.isEmpty()) return ""
- if (split.size == 1) return split[0].capitalize()
+ if (split.size == 1) return split[0].capitalize(Locale.US)
return split.joinToCamelCase()
}
@@ -46,3 +48,17 @@
.map(String::trim)
.joinToCamelCaseAsVar()
}
+
+// TODO: Replace this with the function from the Kotlin stdlib once the API becomes stable
+fun String.capitalize(locale: Locale): String = if (isNotEmpty() && this[0].isLowerCase()) {
+ substring(0, 1).toUpperCase(locale) + substring(1)
+} else {
+ this
+}
+
+// TODO: Replace this with the function from the Kotlin stdlib once the API becomes stable
+fun String.decapitalize(locale: Locale): String = if (isNotEmpty() && this[0].isUpperCase()) {
+ substring(0, 1).toLowerCase(locale) + substring(1)
+} else {
+ this
+}
diff --git a/appsearch/appsearch/src/main/java/androidx/appsearch/impl/package-info.java b/room/compiler/src/main/kotlin/androidx/room/ext/xtype_ext.kt
similarity index 60%
copy from appsearch/appsearch/src/main/java/androidx/appsearch/impl/package-info.java
copy to room/compiler/src/main/kotlin/androidx/room/ext/xtype_ext.kt
index 68c3b98..a2c674c 100644
--- a/appsearch/appsearch/src/main/java/androidx/appsearch/impl/package-info.java
+++ b/room/compiler/src/main/kotlin/androidx/room/ext/xtype_ext.kt
@@ -14,10 +14,23 @@
* limitations under the License.
*/
-/**
- * @hide
- */
-@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
-package androidx.appsearch.impl;
+package androidx.room.ext
-import androidx.annotation.RestrictTo;
+import androidx.room.compiler.processing.XType
+
+fun XType.isNotVoid() = !isVoid()
+
+/**
+ * Returns `true` if this type represents a valid resolvable type.
+ */
+fun XType.isNotError() = !isError()
+
+/**
+ * Returns `true` if this is not the None type.
+ */
+fun XType.isNotNone() = !isNone()
+
+/**
+ * Returns `true` if this is not `byte` type.
+ */
+fun XType.isNotByte() = !isByte()
diff --git a/room/compiler/src/main/kotlin/androidx/room/processor/PojoProcessor.kt b/room/compiler/src/main/kotlin/androidx/room/processor/PojoProcessor.kt
index 774c895..d42478e 100644
--- a/room/compiler/src/main/kotlin/androidx/room/processor/PojoProcessor.kt
+++ b/room/compiler/src/main/kotlin/androidx/room/processor/PojoProcessor.kt
@@ -29,6 +29,7 @@
import androidx.room.compiler.processing.XVariableElement
import androidx.room.compiler.processing.asDeclaredType
import androidx.room.compiler.processing.isCollection
+import androidx.room.ext.isNotVoid
import androidx.room.processor.ProcessorErrors.CANNOT_FIND_GETTER_FOR_FIELD
import androidx.room.processor.ProcessorErrors.CANNOT_FIND_SETTER_FOR_FIELD
import androidx.room.processor.ProcessorErrors.POJO_FIELD_HAS_DUPLICATE_COLUMN_NAME
diff --git a/room/compiler/src/main/kotlin/androidx/room/processor/QueryMethodProcessor.kt b/room/compiler/src/main/kotlin/androidx/room/processor/QueryMethodProcessor.kt
index 88bbb26..e4ea3b1 100644
--- a/room/compiler/src/main/kotlin/androidx/room/processor/QueryMethodProcessor.kt
+++ b/room/compiler/src/main/kotlin/androidx/room/processor/QueryMethodProcessor.kt
@@ -25,6 +25,7 @@
import androidx.room.compiler.processing.XDeclaredType
import androidx.room.compiler.processing.XMethodElement
import androidx.room.compiler.processing.XType
+import androidx.room.ext.isNotError
import androidx.room.solver.query.result.PojoRowAdapter
import androidx.room.verifier.DatabaseVerificationErrors
import androidx.room.verifier.DatabaseVerifier
diff --git a/room/compiler/src/main/kotlin/androidx/room/processor/TableEntityProcessor.kt b/room/compiler/src/main/kotlin/androidx/room/processor/TableEntityProcessor.kt
index f66dcc6..feedb3a 100644
--- a/room/compiler/src/main/kotlin/androidx/room/processor/TableEntityProcessor.kt
+++ b/room/compiler/src/main/kotlin/androidx/room/processor/TableEntityProcessor.kt
@@ -20,6 +20,7 @@
import androidx.room.parser.SqlParser
import androidx.room.compiler.processing.XType
import androidx.room.compiler.processing.XTypeElement
+import androidx.room.ext.isNotNone
import androidx.room.processor.EntityProcessor.Companion.createIndexName
import androidx.room.processor.EntityProcessor.Companion.extractForeignKeys
import androidx.room.processor.EntityProcessor.Companion.extractIndices
diff --git a/room/compiler/src/main/kotlin/androidx/room/solver/TypeAdapterStore.kt b/room/compiler/src/main/kotlin/androidx/room/solver/TypeAdapterStore.kt
index c82aac1..72b8339 100644
--- a/room/compiler/src/main/kotlin/androidx/room/solver/TypeAdapterStore.kt
+++ b/room/compiler/src/main/kotlin/androidx/room/solver/TypeAdapterStore.kt
@@ -25,6 +25,7 @@
import androidx.room.compiler.processing.asDeclaredType
import androidx.room.compiler.processing.isArray
import androidx.room.compiler.processing.isDeclared
+import androidx.room.ext.isNotByte
import androidx.room.processor.Context
import androidx.room.processor.EntityProcessor
import androidx.room.processor.FieldProcessor
diff --git a/room/compiler/src/main/kotlin/androidx/room/solver/query/result/PojoRowAdapter.kt b/room/compiler/src/main/kotlin/androidx/room/solver/query/result/PojoRowAdapter.kt
index 129aa5b..aed5aea 100644
--- a/room/compiler/src/main/kotlin/androidx/room/solver/query/result/PojoRowAdapter.kt
+++ b/room/compiler/src/main/kotlin/androidx/room/solver/query/result/PojoRowAdapter.kt
@@ -32,8 +32,10 @@
import androidx.room.vo.Warning
import androidx.room.vo.findFieldByColumnName
import androidx.room.writer.FieldReadWriteWriter
+import capitalize
import com.squareup.javapoet.TypeName
import stripNonJava
+import java.util.Locale
/**
* Creates the entity from the given info.
@@ -117,7 +119,8 @@
override fun onCursorReady(cursorVarName: String, scope: CodeGenScope) {
mapping.fieldsWithIndices = mapping.matchedFields.map {
- val indexVar = scope.getTmpVar("_cursorIndexOf${it.name.stripNonJava().capitalize()}")
+ val indexVar = scope.getTmpVar(
+ "_cursorIndexOf${it.name.stripNonJava().capitalize(Locale.US)}")
val indexMethod = if (info == null) {
"getColumnIndex"
} else {
diff --git a/room/compiler/src/main/kotlin/androidx/room/solver/shortcut/result/InsertMethodAdapter.kt b/room/compiler/src/main/kotlin/androidx/room/solver/shortcut/result/InsertMethodAdapter.kt
index 2309c5f..a9a6590 100644
--- a/room/compiler/src/main/kotlin/androidx/room/solver/shortcut/result/InsertMethodAdapter.kt
+++ b/room/compiler/src/main/kotlin/androidx/room/solver/shortcut/result/InsertMethodAdapter.kt
@@ -16,14 +16,14 @@
package androidx.room.solver.shortcut.result
+import androidx.room.compiler.processing.XType
+import androidx.room.compiler.processing.asDeclaredType
+import androidx.room.compiler.processing.isArray
import androidx.room.ext.KotlinTypeNames
import androidx.room.ext.L
import androidx.room.ext.N
import androidx.room.ext.T
import androidx.room.ext.typeName
-import androidx.room.compiler.processing.XType
-import androidx.room.compiler.processing.asDeclaredType
-import androidx.room.compiler.processing.isArray
import androidx.room.solver.CodeGenScope
import androidx.room.vo.ShortcutQueryParameter
import com.squareup.javapoet.ArrayTypeName
@@ -80,7 +80,6 @@
@Suppress("PLATFORM_CLASS_MAPPED_TO_KOTLIN")
private fun getInsertionType(returnType: XType): InsertionType? {
-
return if (returnType.isVoid()) {
InsertionType.INSERT_VOID
} else if (returnType.isVoidObject()) {
@@ -88,17 +87,20 @@
} else if (returnType.isKotlinUnit()) {
InsertionType.INSERT_UNIT
} else if (returnType.isArray()) {
- val arrayType = returnType.asArray()
- val param = arrayType.componentType
- when {
- param.isPrimitiveLong() -> InsertionType.INSERT_ID_ARRAY
- param.isBoxedLong() -> InsertionType.INSERT_ID_ARRAY_BOX
- else -> null
+ val param = returnType.componentType
+ if (param.isLong()) {
+ if (param.typeName == TypeName.LONG) {
+ InsertionType.INSERT_ID_ARRAY
+ } else {
+ InsertionType.INSERT_ID_ARRAY_BOX
+ }
+ } else {
+ null
}
} else if (returnType.isList()) {
val declared = returnType.asDeclaredType()
val param = declared.typeArguments.first()
- if (param.isBoxedLong()) {
+ if (param.isLong()) {
InsertionType.INSERT_ID_LIST
} else {
null
diff --git a/room/compiler/src/main/kotlin/androidx/room/solver/transaction/binder/InstantTransactionMethodBinder.kt b/room/compiler/src/main/kotlin/androidx/room/solver/transaction/binder/InstantTransactionMethodBinder.kt
index df03a98..984a741 100644
--- a/room/compiler/src/main/kotlin/androidx/room/solver/transaction/binder/InstantTransactionMethodBinder.kt
+++ b/room/compiler/src/main/kotlin/androidx/room/solver/transaction/binder/InstantTransactionMethodBinder.kt
@@ -18,6 +18,7 @@
import androidx.room.ext.N
import androidx.room.compiler.processing.XType
+import androidx.room.ext.isNotVoid
import androidx.room.solver.CodeGenScope
import androidx.room.solver.transaction.result.TransactionMethodAdapter
import com.squareup.javapoet.ClassName
diff --git a/room/compiler/src/main/kotlin/androidx/room/solver/types/CustomTypeConverterWrapper.kt b/room/compiler/src/main/kotlin/androidx/room/solver/types/CustomTypeConverterWrapper.kt
index 90bef99..aa1e4e5 100644
--- a/room/compiler/src/main/kotlin/androidx/room/solver/types/CustomTypeConverterWrapper.kt
+++ b/room/compiler/src/main/kotlin/androidx/room/solver/types/CustomTypeConverterWrapper.kt
@@ -24,6 +24,8 @@
import androidx.room.writer.ClassWriter
import com.squareup.javapoet.ClassName
import com.squareup.javapoet.FieldSpec
+import decapitalize
+import java.util.Locale
import javax.lang.model.element.Modifier
/**
@@ -51,7 +53,7 @@
}
fun typeConverter(scope: CodeGenScope): FieldSpec {
- val baseName = (custom.typeName as ClassName).simpleName().decapitalize()
+ val baseName = (custom.typeName as ClassName).simpleName().decapitalize(Locale.US)
return scope.writer.getOrCreateField(object : ClassWriter.SharedFieldSpec(
baseName, custom.typeName) {
override fun getUniqueKey(): String {
diff --git a/room/compiler/src/main/kotlin/androidx/room/solver/types/PrimitiveColumnTypeAdapter.kt b/room/compiler/src/main/kotlin/androidx/room/solver/types/PrimitiveColumnTypeAdapter.kt
index 2dc78b2..22b6cf3 100644
--- a/room/compiler/src/main/kotlin/androidx/room/solver/types/PrimitiveColumnTypeAdapter.kt
+++ b/room/compiler/src/main/kotlin/androidx/room/solver/types/PrimitiveColumnTypeAdapter.kt
@@ -22,6 +22,7 @@
import androidx.room.compiler.processing.XProcessingEnv
import androidx.room.compiler.processing.XType
import androidx.room.solver.CodeGenScope
+import capitalize
import com.squareup.javapoet.TypeName.BYTE
import com.squareup.javapoet.TypeName.CHAR
import com.squareup.javapoet.TypeName.DOUBLE
@@ -29,6 +30,7 @@
import com.squareup.javapoet.TypeName.INT
import com.squareup.javapoet.TypeName.LONG
import com.squareup.javapoet.TypeName.SHORT
+import java.util.Locale
/**
* Adapters for all primitives that has direct cursor mappings.
@@ -39,7 +41,7 @@
val stmtSetter: String,
typeAffinity: SQLTypeAffinity
) : ColumnTypeAdapter(out, typeAffinity) {
- val cast = if (cursorGetter == "get${out.typeName.toString().capitalize()}")
+ val cast = if (cursorGetter == "get${out.typeName.toString().capitalize(Locale.US)}")
""
else
"(${out.typeName}) "
diff --git a/room/compiler/src/main/kotlin/androidx/room/verifier/jdbc_ext.kt b/room/compiler/src/main/kotlin/androidx/room/verifier/jdbc_ext.kt
index 4e07439..c1a7c33 100644
--- a/room/compiler/src/main/kotlin/androidx/room/verifier/jdbc_ext.kt
+++ b/room/compiler/src/main/kotlin/androidx/room/verifier/jdbc_ext.kt
@@ -20,6 +20,7 @@
import java.sql.ResultSet
import java.sql.ResultSetMetaData
import java.sql.SQLException
+import java.util.Locale
internal fun <T> ResultSet.collect(f: (ResultSet) -> T): List<T> {
val result = arrayListOf<T>()
@@ -50,7 +51,7 @@
private fun PreparedStatement.tryGetAffinity(columnIndex: Int): SQLTypeAffinity {
return try {
- SQLTypeAffinity.valueOf(metaData.getColumnTypeName(columnIndex).capitalize())
+ SQLTypeAffinity.valueOf(metaData.getColumnTypeName(columnIndex).capitalize(Locale.US))
} catch (ex: IllegalArgumentException) {
SQLTypeAffinity.NULL
}
diff --git a/room/compiler/src/main/kotlin/androidx/room/vo/Field.kt b/room/compiler/src/main/kotlin/androidx/room/vo/Field.kt
index c5eca64..529bebd 100644
--- a/room/compiler/src/main/kotlin/androidx/room/vo/Field.kt
+++ b/room/compiler/src/main/kotlin/androidx/room/vo/Field.kt
@@ -24,7 +24,10 @@
import androidx.room.compiler.processing.XVariableElement
import androidx.room.solver.types.CursorValueReader
import androidx.room.solver.types.StatementValueBinder
+import capitalize
import com.squareup.javapoet.TypeName
+import decapitalize
+import java.util.Locale
// used in cache matching, must stay as a data class or implement equals
data class Field(
@@ -93,15 +96,15 @@
result.add(name.substring(1))
}
if (name.startsWith("m") && name[1].isUpperCase()) {
- result.add(name.substring(1).decapitalize())
+ result.add(name.substring(1).decapitalize(Locale.US))
}
if (typeName == TypeName.BOOLEAN || typeName == TypeName.BOOLEAN.box()) {
if (name.length > 2 && name.startsWith("is") && name[2].isUpperCase()) {
- result.add(name.substring(2).decapitalize())
+ result.add(name.substring(2).decapitalize(Locale.US))
}
if (name.length > 3 && name.startsWith("has") && name[3].isUpperCase()) {
- result.add(name.substring(3).decapitalize())
+ result.add(name.substring(3).decapitalize(Locale.US))
}
}
}
@@ -109,10 +112,10 @@
}
val getterNameWithVariations by lazy {
- nameWithVariations.map { "get${it.capitalize()}" } +
+ nameWithVariations.map { "get${it.capitalize(Locale.US)}" } +
if (typeName == TypeName.BOOLEAN || typeName == TypeName.BOOLEAN.box()) {
nameWithVariations.flatMap {
- listOf("is${it.capitalize()}", "has${it.capitalize()}")
+ listOf("is${it.capitalize(Locale.US)}", "has${it.capitalize(Locale.US)}")
}
} else {
emptyList()
@@ -120,7 +123,7 @@
}
val setterNameWithVariations by lazy {
- nameWithVariations.map { "set${it.capitalize()}" }
+ nameWithVariations.map { "set${it.capitalize(Locale.US)}" }
}
/**
diff --git a/room/compiler/src/main/kotlin/androidx/room/vo/FtsOptions.kt b/room/compiler/src/main/kotlin/androidx/room/vo/FtsOptions.kt
index c886887..36819e6 100644
--- a/room/compiler/src/main/kotlin/androidx/room/vo/FtsOptions.kt
+++ b/room/compiler/src/main/kotlin/androidx/room/vo/FtsOptions.kt
@@ -19,6 +19,7 @@
import androidx.room.FtsOptions.MatchInfo
import androidx.room.FtsOptions.Order
import androidx.room.migration.bundle.FtsOptionsBundle
+import java.util.Locale
data class FtsOptions(
val tokenizer: String,
@@ -62,7 +63,7 @@
}
if (matchInfo != MatchInfo.FTS4) {
- add("matchinfo=${matchInfo.name.toLowerCase()}")
+ add("matchinfo=${matchInfo.name.toLowerCase(Locale.US)}")
}
notIndexedColumns.forEach {
diff --git a/room/compiler/src/main/kotlin/androidx/room/vo/RawQueryMethod.kt b/room/compiler/src/main/kotlin/androidx/room/vo/RawQueryMethod.kt
index dc9635e..a19be5e 100644
--- a/room/compiler/src/main/kotlin/androidx/room/vo/RawQueryMethod.kt
+++ b/room/compiler/src/main/kotlin/androidx/room/vo/RawQueryMethod.kt
@@ -20,6 +20,7 @@
import androidx.room.ext.SupportDbTypeNames
import androidx.room.compiler.processing.XMethodElement
import androidx.room.compiler.processing.XType
+import androidx.room.ext.isNotVoid
import androidx.room.solver.query.result.QueryResultBinder
import com.squareup.javapoet.TypeName
diff --git a/room/compiler/src/main/kotlin/androidx/room/vo/RelationCollector.kt b/room/compiler/src/main/kotlin/androidx/room/vo/RelationCollector.kt
index e90359c..f182418 100644
--- a/room/compiler/src/main/kotlin/androidx/room/vo/RelationCollector.kt
+++ b/room/compiler/src/main/kotlin/androidx/room/vo/RelationCollector.kt
@@ -37,6 +37,7 @@
import androidx.room.verifier.DatabaseVerificationErrors
import androidx.room.writer.QueryWriter
import androidx.room.writer.RelationCollectorMethodWriter
+import capitalize
import com.squareup.javapoet.ClassName
import com.squareup.javapoet.CodeBlock
import com.squareup.javapoet.ParameterizedTypeName
@@ -45,6 +46,7 @@
import java.nio.ByteBuffer
import java.util.ArrayList
import java.util.HashSet
+import java.util.Locale
/**
* Internal class that is used to manage fetching 1/N to N relationships.
@@ -66,7 +68,7 @@
fun writeInitCode(scope: CodeGenScope) {
varName = scope.getTmpVar(
- "_collection${relation.field.getPath().stripNonJava().capitalize()}")
+ "_collection${relation.field.getPath().stripNonJava().capitalize(Locale.US)}")
scope.builder().apply {
addStatement("final $T $L = new $T()", mapTypeName, varName, mapTypeName)
}
@@ -85,7 +87,7 @@
readKey(cursorVarName, indexVar, scope) { tmpVar ->
if (relationTypeIsCollection) {
val tmpCollectionVar = scope.getTmpVar(
- "_tmp${relation.field.name.stripNonJava().capitalize()}Collection")
+ "_tmp${relation.field.name.stripNonJava().capitalize(Locale.US)}Collection")
addStatement("$T $L = $L.get($L)", relationTypeName, tmpCollectionVar,
varName, tmpVar)
beginControlFlow("if ($L == null)", tmpCollectionVar).apply {
@@ -111,7 +113,7 @@
}?.indexVar
val tmpvarNameSuffix = if (relationTypeIsCollection) "Collection" else ""
val tmpRelationVar = scope.getTmpVar(
- "_tmp${relation.field.name.stripNonJava().capitalize()}$tmpvarNameSuffix")
+ "_tmp${relation.field.name.stripNonJava().capitalize(Locale.US)}$tmpvarNameSuffix")
scope.builder().apply {
addStatement("$T $L = null", relationTypeName, tmpRelationVar)
readKey(cursorVarName, indexVar, scope) { tmpVar ->
diff --git a/room/compiler/src/main/kotlin/androidx/room/vo/Warning.kt b/room/compiler/src/main/kotlin/androidx/room/vo/Warning.kt
index d63b495..82ca796 100644
--- a/room/compiler/src/main/kotlin/androidx/room/vo/Warning.kt
+++ b/room/compiler/src/main/kotlin/androidx/room/vo/Warning.kt
@@ -16,6 +16,8 @@
package androidx.room.vo
+import java.util.Locale
+
/**
* Internal representation of supported warnings
*/
@@ -48,7 +50,7 @@
companion object {
val PUBLIC_KEY_MAP = values().associateBy { it.publicKey }
fun fromPublicKey(publicKey: String): Warning? {
- return PUBLIC_KEY_MAP[publicKey.toUpperCase()]
+ return PUBLIC_KEY_MAP[publicKey.toUpperCase(Locale.US)]
}
}
}
diff --git a/room/compiler/src/main/kotlin/androidx/room/writer/DaoWriter.kt b/room/compiler/src/main/kotlin/androidx/room/writer/DaoWriter.kt
index 8f9be1c..d4d0af7 100644
--- a/room/compiler/src/main/kotlin/androidx/room/writer/DaoWriter.kt
+++ b/room/compiler/src/main/kotlin/androidx/room/writer/DaoWriter.kt
@@ -40,6 +40,7 @@
import androidx.room.vo.ShortcutMethod
import androidx.room.vo.TransactionMethod
import androidx.room.vo.WriteQueryMethod
+import capitalize
import com.squareup.javapoet.ClassName
import com.squareup.javapoet.CodeBlock
import com.squareup.javapoet.FieldSpec
@@ -49,6 +50,7 @@
import com.squareup.javapoet.TypeName
import com.squareup.javapoet.TypeSpec
import stripNonJava
+import java.util.Locale
import javax.lang.model.element.Modifier.FINAL
import javax.lang.model.element.Modifier.PRIVATE
import javax.lang.model.element.Modifier.PUBLIC
@@ -508,7 +510,7 @@
}
class PreparedStatementField(val method: QueryMethod) : SharedFieldSpec(
- "preparedStmtOf${method.name.capitalize()}", RoomTypeNames.SHARED_SQLITE_STMT
+ "preparedStmtOf${method.name.capitalize(Locale.US)}", RoomTypeNames.SHARED_SQLITE_STMT
) {
override fun prepare(writer: ClassWriter, builder: FieldSpec.Builder) {
builder.addModifiers(PRIVATE, FINAL)
diff --git a/room/compiler/src/main/kotlin/androidx/room/writer/DatabaseWriter.kt b/room/compiler/src/main/kotlin/androidx/room/writer/DatabaseWriter.kt
index 518575e..0ad8ad1 100644
--- a/room/compiler/src/main/kotlin/androidx/room/writer/DatabaseWriter.kt
+++ b/room/compiler/src/main/kotlin/androidx/room/writer/DatabaseWriter.kt
@@ -36,6 +36,7 @@
import com.squareup.javapoet.ParameterizedTypeName
import com.squareup.javapoet.TypeName
import com.squareup.javapoet.TypeSpec
+import decapitalize
import stripNonJava
import java.util.Locale
import javax.lang.model.element.Modifier.FINAL
@@ -165,7 +166,7 @@
val scope = CodeGenScope(this)
builder.apply {
database.daoMethods.forEach { method ->
- val name = method.dao.typeName.simpleName().decapitalize().stripNonJava()
+ val name = method.dao.typeName.simpleName().decapitalize(Locale.US).stripNonJava()
val fieldName = scope.getTmpVar("_$name")
val field = FieldSpec.builder(method.dao.typeName, fieldName,
PRIVATE, VOLATILE).build()
diff --git a/room/compiler/src/main/kotlin/androidx/room/writer/EntityCursorConverterWriter.kt b/room/compiler/src/main/kotlin/androidx/room/writer/EntityCursorConverterWriter.kt
index 2d37196..9a3b80f 100644
--- a/room/compiler/src/main/kotlin/androidx/room/writer/EntityCursorConverterWriter.kt
+++ b/room/compiler/src/main/kotlin/androidx/room/writer/EntityCursorConverterWriter.kt
@@ -24,11 +24,13 @@
import androidx.room.solver.CodeGenScope
import androidx.room.vo.Entity
import androidx.room.vo.FieldWithIndex
+import capitalize
import com.squareup.javapoet.CodeBlock
import com.squareup.javapoet.MethodSpec
import com.squareup.javapoet.ParameterSpec
import com.squareup.javapoet.TypeName
import stripNonJava
+import java.util.Locale
import javax.lang.model.element.Modifier.PRIVATE
class EntityCursorConverterWriter(val entity: Entity) : ClassWriter.SharedMethodSpec(
@@ -55,7 +57,7 @@
scope.builder().addStatement("final $T $L", entity.typeName, entityVar)
val fieldsWithIndices = entity.fields.map {
val indexVar = scope.getTmpVar(
- "_cursorIndexOf${it.name.stripNonJava().capitalize()}")
+ "_cursorIndexOf${it.name.stripNonJava().capitalize(Locale.US)}")
scope.builder().addStatement("final $T $L = $N.getColumnIndex($S)",
TypeName.INT, indexVar, cursorParam, it.columnName)
FieldWithIndex(field = it,
diff --git a/room/compiler/src/main/kotlin/androidx/room/writer/EntityInsertionAdapterWriter.kt b/room/compiler/src/main/kotlin/androidx/room/writer/EntityInsertionAdapterWriter.kt
index 43adb6e..0878fd6 100644
--- a/room/compiler/src/main/kotlin/androidx/room/writer/EntityInsertionAdapterWriter.kt
+++ b/room/compiler/src/main/kotlin/androidx/room/writer/EntityInsertionAdapterWriter.kt
@@ -16,6 +16,7 @@
package androidx.room.writer
+import androidx.room.compiler.processing.XNullability
import androidx.room.ext.L
import androidx.room.ext.RoomTypeNames
import androidx.room.ext.S
@@ -46,7 +47,7 @@
val primitiveAutoGenerateField = if (entity.primaryKey.autoGenerateId) {
entity.primaryKey.fields.firstOrNull()?.let { field ->
field.statementBinder?.typeMirror()?.let { binderType ->
- if (binderType.isPrimitive()) {
+ if (binderType.nullability == XNullability.NONNULL) {
field
} else {
null
diff --git a/room/compiler/src/main/kotlin/androidx/room/writer/FieldReadWriteWriter.kt b/room/compiler/src/main/kotlin/androidx/room/writer/FieldReadWriteWriter.kt
index aa4e649..8296746 100644
--- a/room/compiler/src/main/kotlin/androidx/room/writer/FieldReadWriteWriter.kt
+++ b/room/compiler/src/main/kotlin/androidx/room/writer/FieldReadWriteWriter.kt
@@ -27,7 +27,9 @@
import androidx.room.vo.FieldWithIndex
import androidx.room.vo.Pojo
import androidx.room.vo.RelationCollector
+import capitalize
import com.squareup.javapoet.TypeName
+import java.util.Locale
/**
* Handles writing a field into statement or reading it from statement.
@@ -73,7 +75,7 @@
rootNode.directFields = fieldsWithIndices.filter { it.field.parent == null }
val parentNodes = allParents.associate {
Pair(it, Node(
- varName = scope.getTmpVar("_tmp${it.field.name.capitalize()}"),
+ varName = scope.getTmpVar("_tmp${it.field.name.capitalize(Locale.US)}"),
fieldParent = it))
}
parentNodes.values.forEach { node ->
@@ -324,7 +326,8 @@
indexVar, scope)
}
CallType.METHOD -> {
- val tmpField = scope.getTmpVar("_tmp${field.name.capitalize()}")
+ val tmpField = scope.getTmpVar(
+ "_tmp${field.name.capitalize(Locale.US)}")
addStatement("final $T $L", field.setter.type.typeName, tmpField)
reader.readFromCursor(tmpField, cursorVar, indexVar, scope)
addStatement("$L.$L($L)", ownerVar, field.setter.name, tmpField)
@@ -356,7 +359,7 @@
typeName: TypeName,
scope: CodeGenScope
): String {
- val tmpField = scope.getTmpVar("_tmp${field.name.capitalize()}")
+ val tmpField = scope.getTmpVar("_tmp${field.name.capitalize(Locale.US)}")
scope.builder().apply {
addStatement("final $T $L", typeName, tmpField)
if (alwaysExists) {
diff --git a/room/compiler/src/main/kotlin/androidx/room/writer/FtsTableInfoValidationWriter.kt b/room/compiler/src/main/kotlin/androidx/room/writer/FtsTableInfoValidationWriter.kt
index 2d5ffa9..e76424b 100644
--- a/room/compiler/src/main/kotlin/androidx/room/writer/FtsTableInfoValidationWriter.kt
+++ b/room/compiler/src/main/kotlin/androidx/room/writer/FtsTableInfoValidationWriter.kt
@@ -24,13 +24,15 @@
import androidx.room.ext.T
import androidx.room.ext.typeName
import androidx.room.vo.FtsEntity
+import capitalize
import com.squareup.javapoet.ParameterSpec
import com.squareup.javapoet.ParameterizedTypeName
import stripNonJava
+import java.util.Locale
class FtsTableInfoValidationWriter(val entity: FtsEntity) : ValidationWriter() {
override fun write(dbParam: ParameterSpec, scope: CountingCodeGenScope) {
- val suffix = entity.tableName.stripNonJava().capitalize()
+ val suffix = entity.tableName.stripNonJava().capitalize(Locale.US)
val expectedInfoVar = scope.getTmpVar("_info$suffix")
scope.builder().apply {
val columnListVar = scope.getTmpVar("_columns$suffix")
diff --git a/room/compiler/src/main/kotlin/androidx/room/writer/TableInfoValidationWriter.kt b/room/compiler/src/main/kotlin/androidx/room/writer/TableInfoValidationWriter.kt
index 578dd43..69923a9 100644
--- a/room/compiler/src/main/kotlin/androidx/room/writer/TableInfoValidationWriter.kt
+++ b/room/compiler/src/main/kotlin/androidx/room/writer/TableInfoValidationWriter.kt
@@ -26,12 +26,14 @@
import androidx.room.parser.SQLTypeAffinity
import androidx.room.vo.Entity
import androidx.room.vo.columnNames
+import capitalize
import com.squareup.javapoet.ParameterSpec
import com.squareup.javapoet.ParameterizedTypeName
import stripNonJava
import java.util.Arrays
import java.util.HashMap
import java.util.HashSet
+import java.util.Locale
class TableInfoValidationWriter(val entity: Entity) : ValidationWriter() {
@@ -40,7 +42,7 @@
}
override fun write(dbParam: ParameterSpec, scope: CountingCodeGenScope) {
- val suffix = entity.tableName.stripNonJava().capitalize()
+ val suffix = entity.tableName.stripNonJava().capitalize(Locale.US)
val expectedInfoVar = scope.getTmpVar("_info$suffix")
scope.builder().apply {
val columnListVar = scope.getTmpVar("_columns$suffix")
diff --git a/room/compiler/src/main/kotlin/androidx/room/writer/ViewInfoValidationWriter.kt b/room/compiler/src/main/kotlin/androidx/room/writer/ViewInfoValidationWriter.kt
index 66c2892..16d68a5 100644
--- a/room/compiler/src/main/kotlin/androidx/room/writer/ViewInfoValidationWriter.kt
+++ b/room/compiler/src/main/kotlin/androidx/room/writer/ViewInfoValidationWriter.kt
@@ -22,13 +22,15 @@
import androidx.room.ext.S
import androidx.room.ext.T
import androidx.room.vo.DatabaseView
+import capitalize
import com.squareup.javapoet.ParameterSpec
import stripNonJava
+import java.util.Locale
class ViewInfoValidationWriter(val view: DatabaseView) : ValidationWriter() {
override fun write(dbParam: ParameterSpec, scope: CountingCodeGenScope) {
- val suffix = view.viewName.stripNonJava().capitalize()
+ val suffix = view.viewName.stripNonJava().capitalize(Locale.US)
scope.builder().apply {
val expectedInfoVar = scope.getTmpVar("_info$suffix")
addStatement("final $T $L = new $T($S, $S)",
diff --git a/room/compiler/src/test/kotlin/androidx/room/ext/ElementExtTest.kt b/room/compiler/src/test/kotlin/androidx/room/ext/ElementExtTest.kt
index 68fe0117..ce99964 100644
--- a/room/compiler/src/test/kotlin/androidx/room/ext/ElementExtTest.kt
+++ b/room/compiler/src/test/kotlin/androidx/room/ext/ElementExtTest.kt
@@ -221,7 +221,6 @@
val processingEnv = invocation.processingEnv
primitiveTypeNames.forEach { primitiveTypeName ->
val typeMirror = processingEnv.requireType(primitiveTypeName)
- assertThat(typeMirror.isPrimitive()).isTrue()
assertThat(typeMirror.typeName).isEqualTo(primitiveTypeName)
assertThat(
typeMirror.boxed().typeName
diff --git a/room/compiler/src/test/kotlin/androidx/room/processor/FieldProcessorTest.kt b/room/compiler/src/test/kotlin/androidx/room/processor/FieldProcessorTest.kt
index 25b6947..4c5939c 100644
--- a/room/compiler/src/test/kotlin/androidx/room/processor/FieldProcessorTest.kt
+++ b/room/compiler/src/test/kotlin/androidx/room/processor/FieldProcessorTest.kt
@@ -38,6 +38,7 @@
import org.junit.runners.JUnit4
import org.mockito.Mockito.mock
import simpleRun
+import java.util.Locale
@Suppress("HasPlatformType")
@RunWith(JUnit4::class)
@@ -181,7 +182,7 @@
fun primitiveArray() {
ALL_PRIMITIVES.forEach { primitive ->
singleEntity("@TypeConverters(foo.bar.MyConverter.class) " +
- "${primitive.toString().toLowerCase()}[] arr;") { field, invocation ->
+ "${primitive.toString().toLowerCase(Locale.US)}[] arr;") { field, invocation ->
assertThat(field, `is`(
Field(name = "arr",
type = invocation.processingEnv.getArrayType(primitive),
diff --git a/room/compiler/src/test/kotlin/androidx/room/writer/DaoWriterTest.kt b/room/compiler/src/test/kotlin/androidx/room/writer/DaoWriterTest.kt
index 4370c29..0397a3f 100644
--- a/room/compiler/src/test/kotlin/androidx/room/writer/DaoWriterTest.kt
+++ b/room/compiler/src/test/kotlin/androidx/room/writer/DaoWriterTest.kt
@@ -28,6 +28,7 @@
import org.junit.Test
import org.junit.runner.RunWith
import org.junit.runners.JUnit4
+import java.util.Locale
import javax.tools.JavaFileObject
@RunWith(JUnit4::class)
@@ -44,6 +45,17 @@
}
@Test
+ fun complexDao_turkishLocale() {
+ val originalLocale = Locale.getDefault()
+ try {
+ Locale.setDefault(Locale("tr")) // Turkish has special upper/lowercase i chars
+ complexDao()
+ } finally {
+ Locale.setDefault(originalLocale)
+ }
+ }
+
+ @Test
fun writerDao() {
singleDao(
loadJavaCode("daoWriter/input/WriterDao.java", "foo.bar.WriterDao")
diff --git a/room/integration-tests/testapp/src/androidTest/java/androidx/room/integration/testapp/PKeyTestDatabase.java b/room/integration-tests/testapp/src/androidTest/java/androidx/room/integration/testapp/PKeyTestDatabase.java
index 233f270..ad3f678 100644
--- a/room/integration-tests/testapp/src/androidTest/java/androidx/room/integration/testapp/PKeyTestDatabase.java
+++ b/room/integration-tests/testapp/src/androidTest/java/androidx/room/integration/testapp/PKeyTestDatabase.java
@@ -68,8 +68,13 @@
@Insert
long[] insertAndGetIds(IntegerAutoIncPKeyEntity... item);
+ @Insert
+ Long[] insertAndGetIdsBoxed(IntegerAutoIncPKeyEntity... item);
+
@Query("select data from IntegerAutoIncPKeyEntity WHERE pKey IN(:ids)")
List<String> loadDataById(long... ids);
+ @Query("select data from IntegerAutoIncPKeyEntity WHERE pKey IN(:ids)")
+ List<String> loadDataById(Long[] ids);
}
@Dao
diff --git a/room/integration-tests/testapp/src/androidTest/java/androidx/room/integration/testapp/test/PrepackageTest.java b/room/integration-tests/testapp/src/androidTest/java/androidx/room/integration/testapp/test/PrepackageTest.java
index 725df0e..85ec830 100644
--- a/room/integration-tests/testapp/src/androidTest/java/androidx/room/integration/testapp/test/PrepackageTest.java
+++ b/room/integration-tests/testapp/src/androidTest/java/androidx/room/integration/testapp/test/PrepackageTest.java
@@ -570,7 +570,7 @@
public void onCreateFromAsset_calledOnOpenPrepackagedDatabase() {
Context context = ApplicationProvider.getApplicationContext();
context.deleteDatabase("products.db");
- TestPrepackagedCallback callback = new TestPrepackagedCallback();
+ TestPrepackagedDatabaseCallback callback = new TestPrepackagedDatabaseCallback();
ProductsDatabase database = Room.databaseBuilder(
context, ProductsDatabase.class, "products.db")
.createFromAsset("databases/products_v1.db", callback)
@@ -594,7 +594,7 @@
InputStream toCopyInput = context.getAssets().open("databases/products_v1.db");
copyAsset(toCopyInput, dataDbFile);
- TestPrepackagedCallback callback = new TestPrepackagedCallback();
+ TestPrepackagedDatabaseCallback callback = new TestPrepackagedDatabaseCallback();
ProductsDatabase database = Room.databaseBuilder(
context, ProductsDatabase.class, "products_external.db")
.createFromFile(dataDbFile, callback)
@@ -621,7 +621,7 @@
return zipInputStream;
};
- TestPrepackagedCallback callback = new TestPrepackagedCallback();
+ TestPrepackagedDatabaseCallback callback = new TestPrepackagedDatabaseCallback();
ProductsDatabase database = Room.databaseBuilder(
context, ProductsDatabase.class, "products.db")
.createFromInputStream(inputStreamCallable, callback)
@@ -644,7 +644,7 @@
ProductsDatabase db1 = null;
ProductsDatabase db2 = null;
try {
- TestPrepackagedCallback callback = new TestPrepackagedCallback();
+ TestPrepackagedDatabaseCallback callback = new TestPrepackagedDatabaseCallback();
db1 = Room.databaseBuilder(
context, ProductsDatabase.class, "products.db")
.createFromAsset("databases/products_v1.db", callback)
@@ -677,14 +677,14 @@
public void onPrepackagedCallbackException_calledOnPrepackagedCallbackWhenOpenedAgain() {
Context context = ApplicationProvider.getApplicationContext();
context.deleteDatabase("products.db");
- TestPrepackagedCallback throwingCallback = new TestPrepackagedCallback() {
+ TestPrepackagedDatabaseCallback throwingCallback = new TestPrepackagedDatabaseCallback() {
@Override
public void onOpenPrepackagedDatabase(@NonNull SupportSQLiteDatabase db) {
throw new RuntimeException("Something went wrong!");
}
};
- TestPrepackagedCallback callback = new TestPrepackagedCallback();
+ TestPrepackagedDatabaseCallback callback = new TestPrepackagedDatabaseCallback();
ProductsDatabase db1 = null;
ProductsDatabase db2 = null;
@@ -743,7 +743,8 @@
}
}
- public static class TestPrepackagedCallback extends RoomDatabase.PrepackagedCallback {
+ public static class TestPrepackagedDatabaseCallback extends
+ RoomDatabase.PrepackagedDatabaseCallback {
int mOpenPrepackagedDatabaseCount;
diff --git a/room/integration-tests/testapp/src/androidTest/java/androidx/room/integration/testapp/test/PrimaryKeyTest.java b/room/integration-tests/testapp/src/androidTest/java/androidx/room/integration/testapp/test/PrimaryKeyTest.java
index 6f62d96..1103190 100644
--- a/room/integration-tests/testapp/src/androidTest/java/androidx/room/integration/testapp/test/PrimaryKeyTest.java
+++ b/room/integration-tests/testapp/src/androidTest/java/androidx/room/integration/testapp/test/PrimaryKeyTest.java
@@ -118,6 +118,9 @@
final long[] ids = mDatabase.integerAutoIncPKeyDao().insertAndGetIds(entity, entity2);
assertThat(mDatabase.integerAutoIncPKeyDao().loadDataById(ids),
is(Arrays.asList("foo", "foo2")));
+ Long[] boxedIds = mDatabase.integerAutoIncPKeyDao().insertAndGetIdsBoxed(entity, entity2);
+ assertThat(mDatabase.integerAutoIncPKeyDao().loadDataById(boxedIds),
+ is(Arrays.asList("foo", "foo2")));
}
@Test
diff --git a/room/runtime/api/api_lint.ignore b/room/runtime/api/api_lint.ignore
index 516051a..4450195 100644
--- a/room/runtime/api/api_lint.ignore
+++ b/room/runtime/api/api_lint.ignore
@@ -27,11 +27,11 @@
ExecutorRegistration: androidx.room.RoomDatabase.Builder#addCallback(androidx.room.RoomDatabase.Callback):
Registration methods should have overload that accepts delivery Executor: `addCallback`
-ExecutorRegistration: androidx.room.RoomDatabase.Builder#createFromAsset(String, androidx.room.RoomDatabase.PrepackagedCallback):
+ExecutorRegistration: androidx.room.RoomDatabase.Builder#createFromAsset(String, androidx.room.RoomDatabase.PrepackagedDatabaseCallback):
Registration methods should have overload that accepts delivery Executor: `createFromAsset`
-ExecutorRegistration: androidx.room.RoomDatabase.Builder#createFromFile(java.io.File, androidx.room.RoomDatabase.PrepackagedCallback):
+ExecutorRegistration: androidx.room.RoomDatabase.Builder#createFromFile(java.io.File, androidx.room.RoomDatabase.PrepackagedDatabaseCallback):
Registration methods should have overload that accepts delivery Executor: `createFromFile`
-ExecutorRegistration: androidx.room.RoomDatabase.Builder#createFromInputStream(java.util.concurrent.Callable<java.io.InputStream>, androidx.room.RoomDatabase.PrepackagedCallback):
+ExecutorRegistration: androidx.room.RoomDatabase.Builder#createFromInputStream(java.util.concurrent.Callable<java.io.InputStream>, androidx.room.RoomDatabase.PrepackagedDatabaseCallback):
Registration methods should have overload that accepts delivery Executor: `createFromInputStream`
diff --git a/room/runtime/api/current.ignore b/room/runtime/api/current.ignore
new file mode 100644
index 0000000..4c380f6
--- /dev/null
+++ b/room/runtime/api/current.ignore
@@ -0,0 +1,3 @@
+// Baseline format: 1.0
+RemovedDeprecatedField: androidx.room.RoomDatabase#mCallbacks:
+ Removed deprecated field androidx.room.RoomDatabase.mCallbacks
diff --git a/room/runtime/api/current.txt b/room/runtime/api/current.txt
index 4cc1b07..6690920 100644
--- a/room/runtime/api/current.txt
+++ b/room/runtime/api/current.txt
@@ -15,7 +15,7 @@
field public final androidx.room.RoomDatabase.MigrationContainer migrationContainer;
field public final boolean multiInstanceInvalidation;
field public final String? name;
- field public final androidx.room.RoomDatabase.PrepackagedCallback? prepackagedCallback;
+ field public final androidx.room.RoomDatabase.PrepackagedDatabaseCallback? prepackagedDatabaseCallback;
field public final java.util.concurrent.Executor queryExecutor;
field public final boolean requireMigration;
field public final androidx.sqlite.db.SupportSQLiteOpenHelper.Factory sqliteOpenHelperFactory;
@@ -64,7 +64,6 @@
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;
}
@@ -74,11 +73,11 @@
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!> createFromAsset(String, androidx.room.RoomDatabase.PrepackagedCallback);
+ method public androidx.room.RoomDatabase.Builder<T!> createFromAsset(String, androidx.room.RoomDatabase.PrepackagedDatabaseCallback);
method public androidx.room.RoomDatabase.Builder<T!> createFromFile(java.io.File);
- method public androidx.room.RoomDatabase.Builder<T!> createFromFile(java.io.File, androidx.room.RoomDatabase.PrepackagedCallback);
+ method public androidx.room.RoomDatabase.Builder<T!> createFromFile(java.io.File, androidx.room.RoomDatabase.PrepackagedDatabaseCallback);
method public androidx.room.RoomDatabase.Builder<T!> createFromInputStream(java.util.concurrent.Callable<java.io.InputStream!>);
- method public androidx.room.RoomDatabase.Builder<T!> createFromInputStream(java.util.concurrent.Callable<java.io.InputStream!>, androidx.room.RoomDatabase.PrepackagedCallback);
+ method public androidx.room.RoomDatabase.Builder<T!> createFromInputStream(java.util.concurrent.Callable<java.io.InputStream!>, androidx.room.RoomDatabase.PrepackagedDatabaseCallback);
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...);
@@ -108,8 +107,8 @@
method public java.util.List<androidx.room.migration.Migration!>? findMigrationPath(int, int);
}
- public abstract static class RoomDatabase.PrepackagedCallback {
- ctor public RoomDatabase.PrepackagedCallback();
+ public abstract static class RoomDatabase.PrepackagedDatabaseCallback {
+ ctor public RoomDatabase.PrepackagedDatabaseCallback();
method public void onOpenPrepackagedDatabase(androidx.sqlite.db.SupportSQLiteDatabase);
}
diff --git a/room/runtime/api/public_plus_experimental_current.txt b/room/runtime/api/public_plus_experimental_current.txt
index 4cc1b07..6690920 100644
--- a/room/runtime/api/public_plus_experimental_current.txt
+++ b/room/runtime/api/public_plus_experimental_current.txt
@@ -15,7 +15,7 @@
field public final androidx.room.RoomDatabase.MigrationContainer migrationContainer;
field public final boolean multiInstanceInvalidation;
field public final String? name;
- field public final androidx.room.RoomDatabase.PrepackagedCallback? prepackagedCallback;
+ field public final androidx.room.RoomDatabase.PrepackagedDatabaseCallback? prepackagedDatabaseCallback;
field public final java.util.concurrent.Executor queryExecutor;
field public final boolean requireMigration;
field public final androidx.sqlite.db.SupportSQLiteOpenHelper.Factory sqliteOpenHelperFactory;
@@ -64,7 +64,6 @@
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;
}
@@ -74,11 +73,11 @@
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!> createFromAsset(String, androidx.room.RoomDatabase.PrepackagedCallback);
+ method public androidx.room.RoomDatabase.Builder<T!> createFromAsset(String, androidx.room.RoomDatabase.PrepackagedDatabaseCallback);
method public androidx.room.RoomDatabase.Builder<T!> createFromFile(java.io.File);
- method public androidx.room.RoomDatabase.Builder<T!> createFromFile(java.io.File, androidx.room.RoomDatabase.PrepackagedCallback);
+ method public androidx.room.RoomDatabase.Builder<T!> createFromFile(java.io.File, androidx.room.RoomDatabase.PrepackagedDatabaseCallback);
method public androidx.room.RoomDatabase.Builder<T!> createFromInputStream(java.util.concurrent.Callable<java.io.InputStream!>);
- method public androidx.room.RoomDatabase.Builder<T!> createFromInputStream(java.util.concurrent.Callable<java.io.InputStream!>, androidx.room.RoomDatabase.PrepackagedCallback);
+ method public androidx.room.RoomDatabase.Builder<T!> createFromInputStream(java.util.concurrent.Callable<java.io.InputStream!>, androidx.room.RoomDatabase.PrepackagedDatabaseCallback);
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...);
@@ -108,8 +107,8 @@
method public java.util.List<androidx.room.migration.Migration!>? findMigrationPath(int, int);
}
- public abstract static class RoomDatabase.PrepackagedCallback {
- ctor public RoomDatabase.PrepackagedCallback();
+ public abstract static class RoomDatabase.PrepackagedDatabaseCallback {
+ ctor public RoomDatabase.PrepackagedDatabaseCallback();
method public void onOpenPrepackagedDatabase(androidx.sqlite.db.SupportSQLiteDatabase);
}
diff --git a/room/runtime/api/restricted_current.txt b/room/runtime/api/restricted_current.txt
index 67dc701..60e1d8c 100644
--- a/room/runtime/api/restricted_current.txt
+++ b/room/runtime/api/restricted_current.txt
@@ -6,7 +6,7 @@
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 @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!>?, String?, java.io.File?);
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!>?, String?, java.io.File?, java.util.concurrent.Callable<java.io.InputStream!>?);
- 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?, java.util.concurrent.Callable<java.io.InputStream!>?, androidx.room.RoomDatabase.PrepackagedCallback?);
+ 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?, java.util.concurrent.Callable<java.io.InputStream!>?, androidx.room.RoomDatabase.PrepackagedDatabaseCallback?);
method public boolean isMigrationRequired(int, int);
method @Deprecated public boolean isMigrationRequiredFrom(int);
field public final boolean allowDestructiveMigrationOnDowngrade;
@@ -20,7 +20,7 @@
field public final androidx.room.RoomDatabase.MigrationContainer migrationContainer;
field public final boolean multiInstanceInvalidation;
field public final String? name;
- field public final androidx.room.RoomDatabase.PrepackagedCallback? prepackagedCallback;
+ field public final androidx.room.RoomDatabase.PrepackagedDatabaseCallback? prepackagedDatabaseCallback;
field public final java.util.concurrent.Executor queryExecutor;
field public final boolean requireMigration;
field public final androidx.sqlite.db.SupportSQLiteOpenHelper.Factory sqliteOpenHelperFactory;
@@ -105,7 +105,7 @@
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 @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) protected java.util.List<androidx.room.RoomDatabase.Callback!>? mCallbacks;
field @Deprecated protected volatile androidx.sqlite.db.SupportSQLiteDatabase! mDatabase;
}
@@ -115,11 +115,11 @@
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!> createFromAsset(String, androidx.room.RoomDatabase.PrepackagedCallback);
+ method public androidx.room.RoomDatabase.Builder<T!> createFromAsset(String, androidx.room.RoomDatabase.PrepackagedDatabaseCallback);
method public androidx.room.RoomDatabase.Builder<T!> createFromFile(java.io.File);
- method public androidx.room.RoomDatabase.Builder<T!> createFromFile(java.io.File, androidx.room.RoomDatabase.PrepackagedCallback);
+ method public androidx.room.RoomDatabase.Builder<T!> createFromFile(java.io.File, androidx.room.RoomDatabase.PrepackagedDatabaseCallback);
method public androidx.room.RoomDatabase.Builder<T!> createFromInputStream(java.util.concurrent.Callable<java.io.InputStream!>);
- method public androidx.room.RoomDatabase.Builder<T!> createFromInputStream(java.util.concurrent.Callable<java.io.InputStream!>, androidx.room.RoomDatabase.PrepackagedCallback);
+ method public androidx.room.RoomDatabase.Builder<T!> createFromInputStream(java.util.concurrent.Callable<java.io.InputStream!>, androidx.room.RoomDatabase.PrepackagedDatabaseCallback);
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...);
@@ -149,8 +149,8 @@
method public java.util.List<androidx.room.migration.Migration!>? findMigrationPath(int, int);
}
- public abstract static class RoomDatabase.PrepackagedCallback {
- ctor public RoomDatabase.PrepackagedCallback();
+ public abstract static class RoomDatabase.PrepackagedDatabaseCallback {
+ ctor public RoomDatabase.PrepackagedDatabaseCallback();
method public void onOpenPrepackagedDatabase(androidx.sqlite.db.SupportSQLiteDatabase);
}
diff --git a/room/runtime/src/main/java/androidx/room/DatabaseConfiguration.java b/room/runtime/src/main/java/androidx/room/DatabaseConfiguration.java
index 6db36b2d..753ae77 100644
--- a/room/runtime/src/main/java/androidx/room/DatabaseConfiguration.java
+++ b/room/runtime/src/main/java/androidx/room/DatabaseConfiguration.java
@@ -63,7 +63,7 @@
public final List<RoomDatabase.Callback> callbacks;
@Nullable
- public final RoomDatabase.PrepackagedCallback prepackagedCallback;
+ public final RoomDatabase.PrepackagedDatabaseCallback prepackagedDatabaseCallback;
/**
* Whether Room should throw an exception for queries run on the main thread.
@@ -136,7 +136,7 @@
* @deprecated Use {@link #DatabaseConfiguration(Context, String,
* SupportSQLiteOpenHelper.Factory, RoomDatabase.MigrationContainer, List, boolean,
* RoomDatabase.JournalMode, Executor, Executor, boolean, boolean, boolean, Set, String, File,
- * Callable)}
+ * Callable, RoomDatabase.PrepackagedDatabaseCallback)}
*
* @param context The application context.
* @param name Name of the database, can be null if it is in memory.
@@ -175,7 +175,7 @@
* @deprecated Use {@link #DatabaseConfiguration(Context, String,
* SupportSQLiteOpenHelper.Factory, RoomDatabase.MigrationContainer, List, boolean,
* RoomDatabase.JournalMode, Executor, Executor, boolean, boolean, boolean, Set, String, File,
- * Callable)}
+ * Callable, RoomDatabase.PrepackagedDatabaseCallback)}
*
* @param context The application context.
* @param name Name of the database, can be null if it is in memory.
@@ -221,7 +221,7 @@
* @deprecated Use {@link #DatabaseConfiguration(Context, String,
* SupportSQLiteOpenHelper.Factory, RoomDatabase.MigrationContainer, List, boolean,
* RoomDatabase.JournalMode, Executor, Executor, boolean, boolean, boolean, Set, String, File,
- * Callable)}
+ * Callable, RoomDatabase.PrepackagedDatabaseCallback)}
*
* @param context The application context.
* @param name Name of the database, can be null if it is in memory.
@@ -271,7 +271,7 @@
* @deprecated Use {@link #DatabaseConfiguration(Context, String,
* SupportSQLiteOpenHelper.Factory, RoomDatabase.MigrationContainer, List, boolean,
* RoomDatabase.JournalMode, Executor, Executor, boolean, boolean, boolean, Set, String, File,
- * Callable, RoomDatabase.PrepackagedCallback)}
+ * Callable, RoomDatabase.PrepackagedDatabaseCallback)}
*
* @param context The application context.
* @param name Name of the database, can be null if it is in memory.
@@ -341,7 +341,7 @@
* @param copyFromFile The pre-packaged database file.
* @param copyFromInputStream The callable to get the input stream from which a
* pre-package database file will be copied from.
- * @param prepackagedCallback The pre-packaged callback.
+ * @param prepackagedDatabaseCallback The pre-packaged callback.
*
* @hide
*/
@@ -362,7 +362,7 @@
@Nullable String copyFromAssetPath,
@Nullable File copyFromFile,
@Nullable Callable<InputStream> copyFromInputStream,
- @Nullable RoomDatabase.PrepackagedCallback prepackagedCallback) {
+ @Nullable RoomDatabase.PrepackagedDatabaseCallback prepackagedDatabaseCallback) {
this.sqliteOpenHelperFactory = sqliteOpenHelperFactory;
this.context = context;
this.name = name;
@@ -379,7 +379,7 @@
this.copyFromAssetPath = copyFromAssetPath;
this.copyFromFile = copyFromFile;
this.copyFromInputStream = copyFromInputStream;
- this.prepackagedCallback = prepackagedCallback;
+ this.prepackagedDatabaseCallback = prepackagedDatabaseCallback;
}
/**
diff --git a/room/runtime/src/main/java/androidx/room/RoomDatabase.java b/room/runtime/src/main/java/androidx/room/RoomDatabase.java
index 7e0f442..6400f85 100644
--- a/room/runtime/src/main/java/androidx/room/RoomDatabase.java
+++ b/room/runtime/src/main/java/androidx/room/RoomDatabase.java
@@ -52,7 +52,6 @@
import java.util.Set;
import java.util.TreeMap;
import java.util.concurrent.Callable;
-import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.Executor;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
@@ -90,8 +89,9 @@
boolean mWriteAheadLoggingEnabled;
/**
- * @deprecated Will be hidden in the next release.
+ * @hide
*/
+ @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP_PREFIX)
@Nullable
@Deprecated
protected List<Callback> mCallbacks;
@@ -129,7 +129,8 @@
}
- private final Map<String, Object> mBackingFieldMap = new ConcurrentHashMap<>();
+ private final Map<String, Object> mBackingFieldMap =
+ Collections.synchronizedMap(new HashMap<>());
/**
* Gets the map for storing extension properties of Kotlin type.
@@ -541,7 +542,7 @@
private final String mName;
private final Context mContext;
private ArrayList<Callback> mCallbacks;
- private PrepackagedCallback mPrepackagedCallback;
+ private PrepackagedDatabaseCallback mPrepackagedDatabaseCallback;
/** The Executor used to run database queries. This should be background-threaded. */
private Executor mQueryExecutor;
@@ -629,8 +630,8 @@
@SuppressLint("BuilderSetStyle") // To keep naming consistency.
public Builder<T> createFromAsset(
@NonNull String databaseFilePath,
- @NonNull PrepackagedCallback callback) {
- mPrepackagedCallback = callback;
+ @NonNull PrepackagedDatabaseCallback callback) {
+ mPrepackagedDatabaseCallback = callback;
mCopyFromAssetPath = databaseFilePath;
return this;
}
@@ -686,8 +687,8 @@
@SuppressLint({"BuilderSetStyle", "StreamFiles"}) // To keep naming consistency.
public Builder<T> createFromFile(
@NonNull File databaseFile,
- @NonNull PrepackagedCallback callback) {
- mPrepackagedCallback = callback;
+ @NonNull PrepackagedDatabaseCallback callback) {
+ mPrepackagedDatabaseCallback = callback;
mCopyFromFile = databaseFile;
return this;
}
@@ -757,8 +758,8 @@
@SuppressLint({"BuilderSetStyle", "LambdaLast"}) // To keep naming consistency.
public Builder<T> createFromInputStream(
@NonNull Callable<InputStream> inputStreamCallable,
- @NonNull PrepackagedCallback callback) {
- mPrepackagedCallback = callback;
+ @NonNull PrepackagedDatabaseCallback callback) {
+ mPrepackagedDatabaseCallback = callback;
mCopyFromInputStream = inputStreamCallable;
return this;
}
@@ -1096,7 +1097,7 @@
mCopyFromAssetPath,
mCopyFromFile,
mCopyFromInputStream,
- mPrepackagedCallback);
+ mPrepackagedDatabaseCallback);
T db = Room.getGeneratedImplementation(mDatabaseClass, DB_IMPL_SUFFIX);
db.init(configuration);
return db;
@@ -1233,13 +1234,13 @@
/**
* Callback for {@link Builder#createFromAsset(String)}, {@link Builder#createFromFile(File)}
* and {@link Builder#createFromInputStream(Callable)}
- *
+ * <p>
* This callback will be invoked after the pre-package DB is copied but before Room had
* a chance to open it and therefore before the {@link RoomDatabase.Callback} methods are
* invoked. This callback can be useful for updating the pre-package DB schema to satisfy
* Room's schema validation.
*/
- public abstract static class PrepackagedCallback {
+ public abstract static class PrepackagedDatabaseCallback {
/**
* Called when the pre-packaged database has been copied.
diff --git a/room/runtime/src/main/java/androidx/room/SQLiteCopyOpenHelper.java b/room/runtime/src/main/java/androidx/room/SQLiteCopyOpenHelper.java
index 57382d1..dc45973 100644
--- a/room/runtime/src/main/java/androidx/room/SQLiteCopyOpenHelper.java
+++ b/room/runtime/src/main/java/androidx/room/SQLiteCopyOpenHelper.java
@@ -212,8 +212,8 @@
+ destinationFile.getAbsolutePath());
}
- // Temporarily open intermediate file database using FrameworkSQLiteOpenHelper and call
- // open pre-packaged callback. If it fails then intermediate file won't be copied making
+ // Temporarily open intermediate database file using FrameworkSQLiteOpenHelper and dispatch
+ // the open pre-packaged callback. If it fails then intermediate file won't be copied making
// invoking pre-packaged callback a transactional operation.
dispatchOnOpenPrepackagedDatabase(intermediateFile, writable);
@@ -225,7 +225,8 @@
}
private void dispatchOnOpenPrepackagedDatabase(File databaseFile, boolean writable) {
- if (mDatabaseConfiguration.prepackagedCallback == null) {
+ if (mDatabaseConfiguration == null
+ || mDatabaseConfiguration.prepackagedDatabaseCallback == null) {
return;
}
@@ -233,8 +234,7 @@
try {
SupportSQLiteDatabase db = writable ? helper.getWritableDatabase() :
helper.getReadableDatabase();
-
- mDatabaseConfiguration.prepackagedCallback.onOpenPrepackagedDatabase(db);
+ mDatabaseConfiguration.prepackagedDatabaseCallback.onOpenPrepackagedDatabase(db);
} finally {
// Close the db and let Room re-open it through a normal path
helper.close();
diff --git a/samples/Support7Demos/src/main/java/com/example/android/supportv7/media/SampleMediaRouterActivity.java b/samples/Support7Demos/src/main/java/com/example/android/supportv7/media/SampleMediaRouterActivity.java
index 0283f7f..e9aaa25 100644
--- a/samples/Support7Demos/src/main/java/com/example/android/supportv7/media/SampleMediaRouterActivity.java
+++ b/samples/Support7Demos/src/main/java/com/example/android/supportv7/media/SampleMediaRouterActivity.java
@@ -226,6 +226,7 @@
// Create a route selector for the type of routes that we care about.
mSelector = new MediaRouteSelector.Builder()
.addControlCategory(MediaControlIntent.CATEGORY_REMOTE_PLAYBACK)
+ .addControlCategory(MediaControlIntent.CATEGORY_LIVE_AUDIO)
.addControlCategory(SampleMediaRouteProvider.CATEGORY_SAMPLE_ROUTE)
.build();
@@ -644,12 +645,7 @@
@Override
public int onPrepareCallbackFlags() {
- // Add the CALLBACK_FLAG_UNFILTERED_EVENTS flag to ensure that we will
- // observe and log all route events including those that are for routes
- // that do not match our selector. This is only for demonstration purposes
- // and should not be needed by most applications.
- return super.onPrepareCallbackFlags()
- | MediaRouter.CALLBACK_FLAG_UNFILTERED_EVENTS;
+ return super.onPrepareCallbackFlags();
}
}
diff --git a/savedstate/savedstate-ktx/api/1.1.0-beta01.txt b/savedstate/savedstate-ktx/api/1.1.0-beta01.txt
new file mode 100644
index 0000000..5950b3f
--- /dev/null
+++ b/savedstate/savedstate-ktx/api/1.1.0-beta01.txt
@@ -0,0 +1,9 @@
+// Signature format: 3.0
+package androidx.savedstate {
+
+ public final class ViewKt {
+ method public static androidx.savedstate.SavedStateRegistryOwner? findViewTreeSavedStateRegistryOwner(android.view.View);
+ }
+
+}
+
diff --git a/savedstate/savedstate-ktx/api/public_plus_experimental_1.1.0-beta01.txt b/savedstate/savedstate-ktx/api/public_plus_experimental_1.1.0-beta01.txt
new file mode 100644
index 0000000..5950b3f
--- /dev/null
+++ b/savedstate/savedstate-ktx/api/public_plus_experimental_1.1.0-beta01.txt
@@ -0,0 +1,9 @@
+// Signature format: 3.0
+package androidx.savedstate {
+
+ public final class ViewKt {
+ method public static androidx.savedstate.SavedStateRegistryOwner? findViewTreeSavedStateRegistryOwner(android.view.View);
+ }
+
+}
+
diff --git a/savedstate/savedstate-ktx/api/res-1.1.0-beta01.txt b/savedstate/savedstate-ktx/api/res-1.1.0-beta01.txt
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/savedstate/savedstate-ktx/api/res-1.1.0-beta01.txt
diff --git a/savedstate/savedstate-ktx/api/restricted_1.1.0-beta01.txt b/savedstate/savedstate-ktx/api/restricted_1.1.0-beta01.txt
new file mode 100644
index 0000000..5950b3f
--- /dev/null
+++ b/savedstate/savedstate-ktx/api/restricted_1.1.0-beta01.txt
@@ -0,0 +1,9 @@
+// Signature format: 3.0
+package androidx.savedstate {
+
+ public final class ViewKt {
+ method public static androidx.savedstate.SavedStateRegistryOwner? findViewTreeSavedStateRegistryOwner(android.view.View);
+ }
+
+}
+
diff --git a/savedstate/savedstate/api/1.1.0-beta01.txt b/savedstate/savedstate/api/1.1.0-beta01.txt
new file mode 100644
index 0000000..e11a77c
--- /dev/null
+++ b/savedstate/savedstate/api/1.1.0-beta01.txt
@@ -0,0 +1,37 @@
+// Signature format: 3.0
+package androidx.savedstate {
+
+ public final class SavedStateRegistry {
+ method @MainThread public android.os.Bundle? consumeRestoredStateForKey(String);
+ method @MainThread public boolean isRestored();
+ method @MainThread public void registerSavedStateProvider(String, androidx.savedstate.SavedStateRegistry.SavedStateProvider);
+ method @MainThread public void runOnNextRecreation(Class<? extends androidx.savedstate.SavedStateRegistry.AutoRecreated>);
+ method @MainThread public void unregisterSavedStateProvider(String);
+ }
+
+ public static interface SavedStateRegistry.AutoRecreated {
+ method public void onRecreated(androidx.savedstate.SavedStateRegistryOwner);
+ }
+
+ public static interface SavedStateRegistry.SavedStateProvider {
+ method public android.os.Bundle saveState();
+ }
+
+ public final class SavedStateRegistryController {
+ method public static androidx.savedstate.SavedStateRegistryController create(androidx.savedstate.SavedStateRegistryOwner);
+ method public androidx.savedstate.SavedStateRegistry getSavedStateRegistry();
+ method @MainThread public void performRestore(android.os.Bundle?);
+ method @MainThread public void performSave(android.os.Bundle);
+ }
+
+ public interface SavedStateRegistryOwner extends androidx.lifecycle.LifecycleOwner {
+ method public androidx.savedstate.SavedStateRegistry getSavedStateRegistry();
+ }
+
+ public final class ViewTreeSavedStateRegistryOwner {
+ method public static androidx.savedstate.SavedStateRegistryOwner? get(android.view.View);
+ method public static void set(android.view.View, androidx.savedstate.SavedStateRegistryOwner?);
+ }
+
+}
+
diff --git a/savedstate/savedstate/api/public_plus_experimental_1.1.0-beta01.txt b/savedstate/savedstate/api/public_plus_experimental_1.1.0-beta01.txt
new file mode 100644
index 0000000..e11a77c
--- /dev/null
+++ b/savedstate/savedstate/api/public_plus_experimental_1.1.0-beta01.txt
@@ -0,0 +1,37 @@
+// Signature format: 3.0
+package androidx.savedstate {
+
+ public final class SavedStateRegistry {
+ method @MainThread public android.os.Bundle? consumeRestoredStateForKey(String);
+ method @MainThread public boolean isRestored();
+ method @MainThread public void registerSavedStateProvider(String, androidx.savedstate.SavedStateRegistry.SavedStateProvider);
+ method @MainThread public void runOnNextRecreation(Class<? extends androidx.savedstate.SavedStateRegistry.AutoRecreated>);
+ method @MainThread public void unregisterSavedStateProvider(String);
+ }
+
+ public static interface SavedStateRegistry.AutoRecreated {
+ method public void onRecreated(androidx.savedstate.SavedStateRegistryOwner);
+ }
+
+ public static interface SavedStateRegistry.SavedStateProvider {
+ method public android.os.Bundle saveState();
+ }
+
+ public final class SavedStateRegistryController {
+ method public static androidx.savedstate.SavedStateRegistryController create(androidx.savedstate.SavedStateRegistryOwner);
+ method public androidx.savedstate.SavedStateRegistry getSavedStateRegistry();
+ method @MainThread public void performRestore(android.os.Bundle?);
+ method @MainThread public void performSave(android.os.Bundle);
+ }
+
+ public interface SavedStateRegistryOwner extends androidx.lifecycle.LifecycleOwner {
+ method public androidx.savedstate.SavedStateRegistry getSavedStateRegistry();
+ }
+
+ public final class ViewTreeSavedStateRegistryOwner {
+ method public static androidx.savedstate.SavedStateRegistryOwner? get(android.view.View);
+ method public static void set(android.view.View, androidx.savedstate.SavedStateRegistryOwner?);
+ }
+
+}
+
diff --git a/savedstate/savedstate/api/res-1.1.0-beta01.txt b/savedstate/savedstate/api/res-1.1.0-beta01.txt
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/savedstate/savedstate/api/res-1.1.0-beta01.txt
diff --git a/savedstate/savedstate/api/restricted_1.1.0-beta01.txt b/savedstate/savedstate/api/restricted_1.1.0-beta01.txt
new file mode 100644
index 0000000..e11a77c
--- /dev/null
+++ b/savedstate/savedstate/api/restricted_1.1.0-beta01.txt
@@ -0,0 +1,37 @@
+// Signature format: 3.0
+package androidx.savedstate {
+
+ public final class SavedStateRegistry {
+ method @MainThread public android.os.Bundle? consumeRestoredStateForKey(String);
+ method @MainThread public boolean isRestored();
+ method @MainThread public void registerSavedStateProvider(String, androidx.savedstate.SavedStateRegistry.SavedStateProvider);
+ method @MainThread public void runOnNextRecreation(Class<? extends androidx.savedstate.SavedStateRegistry.AutoRecreated>);
+ method @MainThread public void unregisterSavedStateProvider(String);
+ }
+
+ public static interface SavedStateRegistry.AutoRecreated {
+ method public void onRecreated(androidx.savedstate.SavedStateRegistryOwner);
+ }
+
+ public static interface SavedStateRegistry.SavedStateProvider {
+ method public android.os.Bundle saveState();
+ }
+
+ public final class SavedStateRegistryController {
+ method public static androidx.savedstate.SavedStateRegistryController create(androidx.savedstate.SavedStateRegistryOwner);
+ method public androidx.savedstate.SavedStateRegistry getSavedStateRegistry();
+ method @MainThread public void performRestore(android.os.Bundle?);
+ method @MainThread public void performSave(android.os.Bundle);
+ }
+
+ public interface SavedStateRegistryOwner extends androidx.lifecycle.LifecycleOwner {
+ method public androidx.savedstate.SavedStateRegistry getSavedStateRegistry();
+ }
+
+ public final class ViewTreeSavedStateRegistryOwner {
+ method public static androidx.savedstate.SavedStateRegistryOwner? get(android.view.View);
+ method public static void set(android.view.View, androidx.savedstate.SavedStateRegistryOwner?);
+ }
+
+}
+
diff --git a/settings.gradle b/settings.gradle
index f4c7341..ff95a5c 100644
--- a/settings.gradle
+++ b/settings.gradle
@@ -79,6 +79,7 @@
includeProject(":appcompat:appcompat-resources", "appcompat/appcompat-resources")
includeProject(":appsearch:appsearch", "appsearch/appsearch")
includeProject(":appsearch:appsearch-compiler", "appsearch/compiler")
+includeProject(":appsearch:appsearch-local-backend", "appsearch/local-backend")
includeProject(":arch:core-common", "arch/core-common")
includeProject(":arch:core-testing", "arch/core-testing")
includeProject(":arch:core-runtime", "arch/core-runtime")
@@ -140,6 +141,8 @@
includeProject(":customview:customview", "customview/customview")
includeProject(":datastore:datastore-core", "datastore/datastore-core")
includeProject(":datastore:datastore-preferences", "datastore/datastore-preferences")
+includeProject(":datastore:datastore-preferences:datastore-preferences-proto",
+ "datastore/datastore-preferences/datastore-preferences-proto")
includeProject(":datastore:datastore-proto", "datastore/datastore-proto")
includeProject(":datastore:datastore-sampleapp", "datastore/datastore-sampleapp")
includeProject(":documentfile:documentfile", "documentfile/documentfile")
@@ -209,7 +212,7 @@
includeProject(":lifecycle:lifecycle-service", "lifecycle/lifecycle-service")
includeProject(":lifecycle:lifecycle-viewmodel", "lifecycle/lifecycle-viewmodel")
includeProject(":lifecycle:lifecycle-viewmodel-ktx", "lifecycle/lifecycle-viewmodel-ktx")
-includeProject(":lifecycle:lifecycle-viewmodel-savedstate","lifecycle/lifecycle-viewmodel-savedstate")
+includeProject(":lifecycle:lifecycle-viewmodel-savedstate", "lifecycle/lifecycle-viewmodel-savedstate")
includeProject(":lint-demos:lint-demo-appcompat", "lint-demos/lint-demo-appcompat")
includeProject(":loader:loader", "loader/loader")
includeProject(":loader:loader-ktx", "loader/loader-ktx")
diff --git a/slices/core/src/main/java/androidx/slice/compat/SlicePermissionActivity.java b/slices/core/src/main/java/androidx/slice/compat/SlicePermissionActivity.java
index 91f22d1..bfd3368 100644
--- a/slices/core/src/main/java/androidx/slice/compat/SlicePermissionActivity.java
+++ b/slices/core/src/main/java/androidx/slice/compat/SlicePermissionActivity.java
@@ -16,7 +16,6 @@
package androidx.slice.compat;
-import android.app.Activity;
import android.content.DialogInterface;
import android.content.DialogInterface.OnClickListener;
import android.content.DialogInterface.OnDismissListener;
@@ -34,6 +33,7 @@
import androidx.annotation.RequiresApi;
import androidx.annotation.RestrictTo;
import androidx.appcompat.app.AlertDialog;
+import androidx.appcompat.app.AppCompatActivity;
import androidx.core.text.BidiFormatter;
import androidx.slice.core.R;
@@ -43,7 +43,7 @@
*/
@RestrictTo(RestrictTo.Scope.LIBRARY)
@RequiresApi(19)
-public class SlicePermissionActivity extends Activity implements OnClickListener,
+public class SlicePermissionActivity extends AppCompatActivity implements OnClickListener,
OnDismissListener {
private static final float MAX_LABEL_SIZE_PX = 500f;
diff --git a/sqlite/sqlite-inspection/src/androidTest/java/androidx/sqlite/inspection/RoomInvalidationRegistryWithoutRoomTest.kt b/sqlite/sqlite-inspection/src/androidTest/java/androidx/sqlite/inspection/RoomInvalidationRegistryWithoutRoomTest.kt
index 126e41e..4e38343 100644
--- a/sqlite/sqlite-inspection/src/androidTest/java/androidx/sqlite/inspection/RoomInvalidationRegistryWithoutRoomTest.kt
+++ b/sqlite/sqlite-inspection/src/androidTest/java/androidx/sqlite/inspection/RoomInvalidationRegistryWithoutRoomTest.kt
@@ -35,23 +35,7 @@
// this does not really assert anything, we just want to make sure it does not crash and
// never makes a call to the environment if Room is not available.
val env = object : InspectorEnvironment {
- override fun registerEntryHook(
- originClass: Class<*>,
- originMethod: String,
- entryHook: ArtToolInterface.EntryHook
- ) {
- throw AssertionError("should never call environment")
- }
-
- override fun <T : Any?> findInstances(clazz: Class<T>): MutableList<T> {
- throw AssertionError("should never call environment")
- }
-
- override fun <T : Any?> registerExitHook(
- originClass: Class<*>,
- originMethod: String,
- exitHook: ArtToolInterface.ExitHook<T>
- ) {
+ override fun artTI(): ArtToolInterface {
throw AssertionError("should never call environment")
}
}
diff --git a/startup/startup-runtime/api/1.0.0-beta01.txt b/startup/startup-runtime/api/1.0.0-beta01.txt
new file mode 100644
index 0000000..5df1c18
--- /dev/null
+++ b/startup/startup-runtime/api/1.0.0-beta01.txt
@@ -0,0 +1,16 @@
+// Signature format: 3.0
+package androidx.startup {
+
+ public final class AppInitializer {
+ method public static androidx.startup.AppInitializer getInstance(android.content.Context);
+ method public <T> T initializeComponent(Class<? extends androidx.startup.Initializer<T!>>);
+ method public boolean isEagerlyInitialized(Class<? extends androidx.startup.Initializer<?>>);
+ }
+
+ public interface Initializer<T> {
+ method public T create(android.content.Context);
+ method public java.util.List<java.lang.Class<? extends androidx.startup.Initializer<?>>!> dependencies();
+ }
+
+}
+
diff --git a/startup/startup-runtime/api/public_plus_experimental_1.0.0-beta01.txt b/startup/startup-runtime/api/public_plus_experimental_1.0.0-beta01.txt
new file mode 100644
index 0000000..5df1c18
--- /dev/null
+++ b/startup/startup-runtime/api/public_plus_experimental_1.0.0-beta01.txt
@@ -0,0 +1,16 @@
+// Signature format: 3.0
+package androidx.startup {
+
+ public final class AppInitializer {
+ method public static androidx.startup.AppInitializer getInstance(android.content.Context);
+ method public <T> T initializeComponent(Class<? extends androidx.startup.Initializer<T!>>);
+ method public boolean isEagerlyInitialized(Class<? extends androidx.startup.Initializer<?>>);
+ }
+
+ public interface Initializer<T> {
+ method public T create(android.content.Context);
+ method public java.util.List<java.lang.Class<? extends androidx.startup.Initializer<?>>!> dependencies();
+ }
+
+}
+
diff --git a/startup/startup-runtime/api/res-1.0.0-beta01.txt b/startup/startup-runtime/api/res-1.0.0-beta01.txt
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/startup/startup-runtime/api/res-1.0.0-beta01.txt
diff --git a/startup/startup-runtime/api/restricted_1.0.0-beta01.txt b/startup/startup-runtime/api/restricted_1.0.0-beta01.txt
new file mode 100644
index 0000000..5df1c18
--- /dev/null
+++ b/startup/startup-runtime/api/restricted_1.0.0-beta01.txt
@@ -0,0 +1,16 @@
+// Signature format: 3.0
+package androidx.startup {
+
+ public final class AppInitializer {
+ method public static androidx.startup.AppInitializer getInstance(android.content.Context);
+ method public <T> T initializeComponent(Class<? extends androidx.startup.Initializer<T!>>);
+ method public boolean isEagerlyInitialized(Class<? extends androidx.startup.Initializer<?>>);
+ }
+
+ public interface Initializer<T> {
+ method public T create(android.content.Context);
+ method public java.util.List<java.lang.Class<? extends androidx.startup.Initializer<?>>!> dependencies();
+ }
+
+}
+
diff --git a/swiperefreshlayout/swiperefreshlayout/api/api_lint.ignore b/swiperefreshlayout/swiperefreshlayout/api/api_lint.ignore
index cdf85a08..3923f22 100644
--- a/swiperefreshlayout/swiperefreshlayout/api/api_lint.ignore
+++ b/swiperefreshlayout/swiperefreshlayout/api/api_lint.ignore
@@ -1,4 +1,8 @@
// Baseline format: 1.0
+CallbackMethodName: androidx.swiperefreshlayout.widget.SwipeRefreshLayout.OnChildScrollUpCallback#canChildScrollUp(androidx.swiperefreshlayout.widget.SwipeRefreshLayout, android.view.View):
+ Callback method names must follow the on<Something> style: canChildScrollUp
+
+
InternalField: androidx.swiperefreshlayout.widget.SwipeRefreshLayout#mFrom:
Internal field mFrom must not be exposed
InternalField: androidx.swiperefreshlayout.widget.SwipeRefreshLayout#mOriginalOffsetTop:
diff --git a/ui/ui-animation-tooling-internal/build.gradle b/ui/ui-animation-tooling-internal/build.gradle
index 4be1456..627cf36 100644
--- a/ui/ui-animation-tooling-internal/build.gradle
+++ b/ui/ui-animation-tooling-internal/build.gradle
@@ -33,7 +33,7 @@
name = "Compose Animation Tooling"
description = "Compose Animation APIs for tooling support. Internal use only."
publish = Publish.SNAPSHOT_ONLY
- mavenVersion = LibraryVersions.UI
+ mavenVersion = LibraryVersions.COMPOSE
mavenGroup = LibraryGroups.UI
generateDocs = false
}
diff --git a/ui/ui-test/api/current.txt b/ui/ui-test/api/current.txt
index 23556f2c..fc092191 100644
--- a/ui/ui-test/api/current.txt
+++ b/ui/ui-test/api/current.txt
@@ -48,7 +48,7 @@
}
public final class AnimationClocksKt {
- method public static androidx.compose.animation.core.MonotonicFrameAnimationClock monotonicFrameAnimationClockOf(kotlin.coroutines.CoroutineContext coroutineContext, androidx.compose.runtime.dispatch.MonotonicFrameClock clock);
+ method @androidx.ui.test.ExperimentalTesting public static androidx.compose.animation.core.MonotonicFrameAnimationClock monotonicFrameAnimationClockOf(kotlin.coroutines.CoroutineContext coroutineContext, androidx.compose.runtime.dispatch.MonotonicFrameClock clock);
}
public final class AssertionsKt {
@@ -106,7 +106,7 @@
}
public final class CoroutineBuildersKt {
- method public static <R> void runBlockingWithManualClock(boolean compatibleWithManualAnimationClock = false, kotlin.jvm.functions.Function3<? super kotlinx.coroutines.CoroutineScope,? super androidx.compose.animation.core.ManualFrameClock,? super kotlin.coroutines.Continuation<? super R>,?> block);
+ method @androidx.ui.test.ExperimentalTesting public static <R> void runBlockingWithManualClock(boolean compatibleWithManualAnimationClock = false, kotlin.jvm.functions.Function3<? super kotlinx.coroutines.CoroutineScope,? super androidx.compose.animation.core.ManualFrameClock,? super kotlin.coroutines.Continuation<? super R>,?> block);
}
public final class DisableTransitions implements org.junit.rules.TestRule {
@@ -316,7 +316,7 @@
property public abstract boolean isPaused;
}
- public final class TestUiDispatcher {
+ @androidx.ui.test.ExperimentalTesting public final class TestUiDispatcher {
method public kotlin.coroutines.CoroutineContext getMain();
property public final kotlin.coroutines.CoroutineContext Main;
field public static final androidx.ui.test.TestUiDispatcher INSTANCE;
diff --git a/ui/ui-test/api/public_plus_experimental_current.txt b/ui/ui-test/api/public_plus_experimental_current.txt
index 23556f2c..fc092191 100644
--- a/ui/ui-test/api/public_plus_experimental_current.txt
+++ b/ui/ui-test/api/public_plus_experimental_current.txt
@@ -48,7 +48,7 @@
}
public final class AnimationClocksKt {
- method public static androidx.compose.animation.core.MonotonicFrameAnimationClock monotonicFrameAnimationClockOf(kotlin.coroutines.CoroutineContext coroutineContext, androidx.compose.runtime.dispatch.MonotonicFrameClock clock);
+ method @androidx.ui.test.ExperimentalTesting public static androidx.compose.animation.core.MonotonicFrameAnimationClock monotonicFrameAnimationClockOf(kotlin.coroutines.CoroutineContext coroutineContext, androidx.compose.runtime.dispatch.MonotonicFrameClock clock);
}
public final class AssertionsKt {
@@ -106,7 +106,7 @@
}
public final class CoroutineBuildersKt {
- method public static <R> void runBlockingWithManualClock(boolean compatibleWithManualAnimationClock = false, kotlin.jvm.functions.Function3<? super kotlinx.coroutines.CoroutineScope,? super androidx.compose.animation.core.ManualFrameClock,? super kotlin.coroutines.Continuation<? super R>,?> block);
+ method @androidx.ui.test.ExperimentalTesting public static <R> void runBlockingWithManualClock(boolean compatibleWithManualAnimationClock = false, kotlin.jvm.functions.Function3<? super kotlinx.coroutines.CoroutineScope,? super androidx.compose.animation.core.ManualFrameClock,? super kotlin.coroutines.Continuation<? super R>,?> block);
}
public final class DisableTransitions implements org.junit.rules.TestRule {
@@ -316,7 +316,7 @@
property public abstract boolean isPaused;
}
- public final class TestUiDispatcher {
+ @androidx.ui.test.ExperimentalTesting public final class TestUiDispatcher {
method public kotlin.coroutines.CoroutineContext getMain();
property public final kotlin.coroutines.CoroutineContext Main;
field public static final androidx.ui.test.TestUiDispatcher INSTANCE;
diff --git a/ui/ui-test/api/restricted_current.txt b/ui/ui-test/api/restricted_current.txt
index 23556f2c..fc092191 100644
--- a/ui/ui-test/api/restricted_current.txt
+++ b/ui/ui-test/api/restricted_current.txt
@@ -48,7 +48,7 @@
}
public final class AnimationClocksKt {
- method public static androidx.compose.animation.core.MonotonicFrameAnimationClock monotonicFrameAnimationClockOf(kotlin.coroutines.CoroutineContext coroutineContext, androidx.compose.runtime.dispatch.MonotonicFrameClock clock);
+ method @androidx.ui.test.ExperimentalTesting public static androidx.compose.animation.core.MonotonicFrameAnimationClock monotonicFrameAnimationClockOf(kotlin.coroutines.CoroutineContext coroutineContext, androidx.compose.runtime.dispatch.MonotonicFrameClock clock);
}
public final class AssertionsKt {
@@ -106,7 +106,7 @@
}
public final class CoroutineBuildersKt {
- method public static <R> void runBlockingWithManualClock(boolean compatibleWithManualAnimationClock = false, kotlin.jvm.functions.Function3<? super kotlinx.coroutines.CoroutineScope,? super androidx.compose.animation.core.ManualFrameClock,? super kotlin.coroutines.Continuation<? super R>,?> block);
+ method @androidx.ui.test.ExperimentalTesting public static <R> void runBlockingWithManualClock(boolean compatibleWithManualAnimationClock = false, kotlin.jvm.functions.Function3<? super kotlinx.coroutines.CoroutineScope,? super androidx.compose.animation.core.ManualFrameClock,? super kotlin.coroutines.Continuation<? super R>,?> block);
}
public final class DisableTransitions implements org.junit.rules.TestRule {
@@ -316,7 +316,7 @@
property public abstract boolean isPaused;
}
- public final class TestUiDispatcher {
+ @androidx.ui.test.ExperimentalTesting public final class TestUiDispatcher {
method public kotlin.coroutines.CoroutineContext getMain();
property public final kotlin.coroutines.CoroutineContext Main;
field public static final androidx.ui.test.TestUiDispatcher INSTANCE;
diff --git a/ui/ui-test/build.gradle b/ui/ui-test/build.gradle
index 465191e..ab3e2fc 100644
--- a/ui/ui-test/build.gradle
+++ b/ui/ui-test/build.gradle
@@ -89,7 +89,7 @@
androidx {
name = "Compose Testing"
publish = Publish.SNAPSHOT_AND_RELEASE
- mavenVersion = LibraryVersions.UI
+ mavenVersion = LibraryVersions.COMPOSE
mavenGroup = LibraryGroups.UI
inceptionYear = "2019"
description = "Compose testing library"
diff --git a/ui/ui-test/src/androidMain/kotlin/androidx/ui/test/AndroidBaseInputDispatcher.kt b/ui/ui-test/src/androidMain/kotlin/androidx/ui/test/AndroidBaseInputDispatcher.kt
index c770634f..dea6057 100644
--- a/ui/ui-test/src/androidMain/kotlin/androidx/ui/test/AndroidBaseInputDispatcher.kt
+++ b/ui/ui-test/src/androidMain/kotlin/androidx/ui/test/AndroidBaseInputDispatcher.kt
@@ -78,11 +78,27 @@
override fun saveState(owner: Owner?) {
if (owner != null && AndroidOwnerRegistry.getUnfilteredOwners().contains(owner)) {
- states[owner] = InputDispatcherState(nextDownTime, partialGesture)
+ states[owner] = InputDispatcherState(nextDownTime, gestureLateness, partialGesture)
}
}
internal var nextDownTime = DownTimeNotSet
+
+ /**
+ * The time difference between enqueuing the first event of the gesture and dispatching it.
+ *
+ * When the first event of a gesture is enqueued, its eventTime is fixed to the current time.
+ * However, there is inevitably some time between enqueuing and dispatching of that event.
+ * This means that event is going to be "late" by [gestureLateness] milliseconds when it is
+ * dispatched. Because the dispatcher wants to align events with the current time, it will
+ * dispatch all events that are late immediately and without delay, until it has reached an
+ * event whose eventTime is in the future (i.e. an event that is "early").
+ *
+ * The [gestureLateness] will be used to offset all events, effectively aligning the first
+ * event with the dispatch time.
+ */
+ internal var gestureLateness: Long? = null
+
internal var partialGesture: PartialGesture? = null
/**
@@ -577,6 +593,7 @@
// TODO(b/157653315): Move restore state to constructor
if (it.partialGesture != null) {
nextDownTime = it.nextDownTime
+ gestureLateness = it.gestureLateness
partialGesture = it.partialGesture
}
}
diff --git a/ui/ui-test/src/androidMain/kotlin/androidx/ui/test/CoroutineBuilders.kt b/ui/ui-test/src/androidMain/kotlin/androidx/ui/test/CoroutineBuilders.kt
index 16801bf..8f25b54 100644
--- a/ui/ui-test/src/androidMain/kotlin/androidx/ui/test/CoroutineBuilders.kt
+++ b/ui/ui-test/src/androidMain/kotlin/androidx/ui/test/CoroutineBuilders.kt
@@ -71,6 +71,7 @@
* first frame immediately upon subscription. Avoid reliance on this if possible. `false` by
* default.
*/
+@ExperimentalTesting
fun <R> runBlockingWithManualClock(
compatibleWithManualAnimationClock: Boolean = false,
block: suspend CoroutineScope.(clock: ManualFrameClock) -> R
diff --git a/ui/ui-test/src/androidMain/kotlin/androidx/ui/test/android/AndroidInputDispatcher.kt b/ui/ui-test/src/androidMain/kotlin/androidx/ui/test/android/AndroidInputDispatcher.kt
index edfc28b..6261084 100644
--- a/ui/ui-test/src/androidMain/kotlin/androidx/ui/test/android/AndroidInputDispatcher.kt
+++ b/ui/ui-test/src/androidMain/kotlin/androidx/ui/test/android/AndroidInputDispatcher.kt
@@ -33,14 +33,18 @@
import kotlinx.coroutines.delay
import kotlinx.coroutines.runBlocking
import kotlinx.coroutines.withContext
+import kotlin.math.max
internal class AndroidInputDispatcher(
private val sendEvent: (MotionEvent) -> Unit
) : AndroidBaseInputDispatcher() {
private val batchLock = Any()
- private var batchedEvents = mutableListOf<MotionEvent>()
+ // Batched events are generated just-in-time, given the "lateness" of the dispatching (see
+ // sendAllSynchronous), so enqueue generators rather than instantiated events
+ private var batchedEvents = mutableListOf<(Long) -> MotionEvent>()
private var acceptEvents = true
+ private var firstEventTime = Long.MAX_VALUE
override val now: Long get() = SystemClock.uptimeMillis()
@@ -106,10 +110,13 @@
"coordinates=$coordinates" +
"), events have already been (or are being) dispatched or disposed"
}
- batchedEvents.add(
+ if (firstEventTime == Long.MAX_VALUE) {
+ firstEventTime = eventTime
+ }
+ batchedEvents.add { lateness ->
MotionEvent.obtain(
- /* downTime = */ downTime,
- /* eventTime = */ eventTime,
+ /* downTime = */ lateness + downTime,
+ /* eventTime = */ lateness + eventTime,
/* action = */ action + (actionIndex shl ACTION_POINTER_INDEX_SHIFT),
/* pointerCount = */ coordinates.size,
/* pointerProperties = */ Array(coordinates.size) {
@@ -130,7 +137,7 @@
/* source = */ 0,
/* flags = */ 0
)
- )
+ }
}
}
@@ -138,34 +145,23 @@
runBlocking {
withContext(AndroidUiDispatcher.Main) {
checkAndStopAcceptingEvents()
- val copy = batchedEvents.toList()
- batchedEvents.clear()
- val iterator = copy.iterator()
- try {
- while (iterator.hasNext()) {
- val event = iterator.next()
- sendAndRecycleEvent(event)
- }
- } finally {
- // In case we were cancelled, or an exception was thrown,
- // stop injecting and recycle all left over events
- while (iterator.hasNext()) {
- try {
- iterator.next().recycle()
- } catch (ignore: Throwable) {
- // ignore all errors, just continue recycling
- }
- }
+
+ // Use gestureLateness if already calculated; calculate, store and use it otherwise
+ val lateness = gestureLateness ?: max(0, now - firstEventTime).also {
+ gestureLateness = it
+ }
+
+ batchedEvents.forEach {
+ sendAndRecycleEvent(it(lateness))
}
}
}
+ // Each invocation of performGesture (Actions.kt) uses a new instance of an input
+ // dispatcher, so we don't have to reset firstEventTime after use
}
override fun dispose() {
- if (stopAcceptingEvents()) {
- batchedEvents.forEach { it.recycle() }
- batchedEvents.clear()
- }
+ stopAcceptingEvents()
}
private fun checkAndStopAcceptingEvents() {
diff --git a/ui/ui-test/src/commonMain/kotlin/androidx/ui/test/AnimationClocks.kt b/ui/ui-test/src/commonMain/kotlin/androidx/ui/test/AnimationClocks.kt
index c036039..7c8b128 100644
--- a/ui/ui-test/src/commonMain/kotlin/androidx/ui/test/AnimationClocks.kt
+++ b/ui/ui-test/src/commonMain/kotlin/androidx/ui/test/AnimationClocks.kt
@@ -72,6 +72,7 @@
*
* @see MonotonicFrameAnimationClock
*/
+@ExperimentalTesting
fun monotonicFrameAnimationClockOf(
coroutineContext: CoroutineContext,
clock: MonotonicFrameClock
diff --git a/ui/ui-test/src/commonMain/kotlin/androidx/ui/test/InputDispatcher.kt b/ui/ui-test/src/commonMain/kotlin/androidx/ui/test/InputDispatcher.kt
index cc9e94a..2395aa3 100644
--- a/ui/ui-test/src/commonMain/kotlin/androidx/ui/test/InputDispatcher.kt
+++ b/ui/ui-test/src/commonMain/kotlin/androidx/ui/test/InputDispatcher.kt
@@ -98,11 +98,15 @@
* @param nextDownTime The downTime of the start of the next gesture, when chaining gestures.
* This property will only be restored if an incomplete gesture was in progress when the state of
* the [InputDispatcher] was saved.
+ * @param gestureLateness The time difference in milliseconds between enqueuing the first event
+ * of the gesture and dispatching it. Depending on the implementation of [InputDispatcher], this
+ * may or may not be used.
* @param partialGesture The state of an incomplete gesture. If no gesture was in progress
* when the state of the [InputDispatcher] was saved, this will be `null`.
*/
internal data class InputDispatcherState(
val nextDownTime: Long,
+ var gestureLateness: Long?,
val partialGesture: PartialGesture?
)
diff --git a/ui/ui-test/src/commonMain/kotlin/androidx/ui/test/TestUiDispatcher.kt b/ui/ui-test/src/commonMain/kotlin/androidx/ui/test/TestUiDispatcher.kt
index 008ea7c..aa79277 100644
--- a/ui/ui-test/src/commonMain/kotlin/androidx/ui/test/TestUiDispatcher.kt
+++ b/ui/ui-test/src/commonMain/kotlin/androidx/ui/test/TestUiDispatcher.kt
@@ -19,6 +19,8 @@
import androidx.compose.runtime.EmbeddingContext
import kotlin.coroutines.CoroutineContext
+// Experimental because it isn't yet clear if the behavior on Robolectric is correct
+@ExperimentalTesting
object TestUiDispatcher {
/**
* The dispatcher to use if you need to dispatch coroutines on the main thread in tests.
diff --git a/ui/ui-tooling/build.gradle b/ui/ui-tooling/build.gradle
index 67c0e15..8d21b69 100644
--- a/ui/ui-tooling/build.gradle
+++ b/ui/ui-tooling/build.gradle
@@ -58,7 +58,7 @@
androidx {
name = "Compose Tooling"
publish = Publish.SNAPSHOT_AND_RELEASE
- mavenVersion = LibraryVersions.UI
+ mavenVersion = LibraryVersions.COMPOSE
mavenGroup = LibraryGroups.UI
inceptionYear = "2019"
description = "Compose tooling library. This library exposes information to our tools for better IDE support."
diff --git a/ui/ui-tooling/src/androidTest/java/androidx/ui/tooling/InspectableTests.kt b/ui/ui-tooling/src/androidTest/java/androidx/ui/tooling/InspectableTests.kt
index 883ab9b..6eb3c07 100644
--- a/ui/ui-tooling/src/androidTest/java/androidx/ui/tooling/InspectableTests.kt
+++ b/ui/ui-tooling/src/androidTest/java/androidx/ui/tooling/InspectableTests.kt
@@ -27,7 +27,10 @@
import androidx.compose.ui.graphics.Color
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.preferredSize
+import androidx.compose.material.Button
+import androidx.compose.material.ModalDrawerLayout
import androidx.compose.runtime.InternalComposeApi
+import androidx.compose.ui.onPositioned
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.height
import androidx.compose.ui.unit.width
@@ -39,6 +42,8 @@
import org.junit.Test
import org.junit.runner.RunWith
import org.junit.runners.JUnit4
+import java.util.concurrent.CountDownLatch
+import java.util.concurrent.TimeUnit
@SmallTest
@RunWith(JUnit4::class)
@@ -340,6 +345,45 @@
names.joinToString()
)
}
+
+ @OptIn(InternalComposeApi::class)
+ @Test // regression test for b/162092315
+ fun inspectingModalDrawerLayout() {
+ val positioned = CountDownLatch(1)
+ val tables = showAndRecord {
+ ModalDrawerLayout(
+ drawerContent = { Text("Something") },
+ bodyContent = {
+ Column(Modifier.onPositioned {
+ positioned.countDown()
+ }) {
+ Text(text = "Hello World", color = Color.Green)
+ Button(onClick = {}) { Text(text = "OK") }
+ }
+ }
+ )
+ }
+
+ assertTrue(positioned.await(2, TimeUnit.SECONDS))
+
+ // Wait for composition to complete
+ activity.runOnUiThread { }
+
+ assertFalse(tables.isNullOrEmpty())
+ assertTrue(tables!!.size > 1)
+
+ val calls = tables.flatMap { table ->
+ if (table.size > 0) table.asTree().asList() else emptyList()
+ }.filter {
+ val location = it.location
+ location != null && location.sourceFile == "InspectableTests.kt"
+ }.map {
+ it.name
+ }
+ assertTrue(calls.contains("Column"))
+ assertTrue(calls.contains("Text"))
+ assertTrue(calls.contains("Button"))
+ }
}
@Suppress("UNUSED_PARAMETER")
diff --git a/ui/ui-tooling/src/androidTest/java/androidx/ui/tooling/ToolingTest.kt b/ui/ui-tooling/src/androidTest/java/androidx/ui/tooling/ToolingTest.kt
index 85aaf74..4728397 100644
--- a/ui/ui-tooling/src/androidTest/java/androidx/ui/tooling/ToolingTest.kt
+++ b/ui/ui-tooling/src/androidTest/java/androidx/ui/tooling/ToolingTest.kt
@@ -18,14 +18,20 @@
import android.os.Handler
import android.os.Looper
+import android.view.ViewGroup
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.onPositioned
import androidx.compose.ui.platform.setContent
import androidx.compose.foundation.Box
import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.runtime.SlotTable
+import androidx.compose.ui.R
+import androidx.compose.ui.platform.AndroidOwner
import org.junit.Before
import org.junit.Rule
+import java.util.Collections
+import java.util.WeakHashMap
import java.util.concurrent.CountDownLatch
import java.util.concurrent.TimeUnit
@@ -64,6 +70,38 @@
// Wait for the UI thread to complete its current work so we know that layout is done.
activityTestRule.onUiThread { }
}
+
+ internal fun showAndRecord(content: @Composable () -> Unit): MutableSet<SlotTable>? {
+
+ positionedLatch = CountDownLatch(1)
+ val map: MutableSet<SlotTable> = Collections.newSetFromMap(
+ WeakHashMap<SlotTable, Boolean>()
+ )
+ activityTestRule.onUiThread {
+ val activity = activity
+ val owner = AndroidOwner(activity, activity, activity).also {
+ activity.setContentView(it.view, ViewGroup.LayoutParams(
+ ViewGroup.LayoutParams.WRAP_CONTENT,
+ ViewGroup.LayoutParams.WRAP_CONTENT
+ ))
+ }
+
+ owner.view.setTag(R.id.inspection_slot_table_set, map)
+ activity.setContent {
+ Box(
+ Modifier.onPositioned { positionedLatch.countDown() }.fillMaxSize(),
+ children = content
+ )
+ }
+
+ // Wait for the layout to be performed
+ positionedLatch.await(1, TimeUnit.SECONDS)
+
+ // Wait for the UI thread to complete its current work so we know that layout is done.
+ activityTestRule.onUiThread { }
+ }
+ return map
+ }
}
// Kotlin IR compiler doesn't seem too happy with auto-conversion from
diff --git a/ui/ui-tooling/src/androidTest/java/androidx/ui/tooling/preview/animation/PreviewAnimationClockTest.kt b/ui/ui-tooling/src/androidTest/java/androidx/ui/tooling/preview/animation/PreviewAnimationClockTest.kt
index c206fd9..d61242e 100644
--- a/ui/ui-tooling/src/androidTest/java/androidx/ui/tooling/preview/animation/PreviewAnimationClockTest.kt
+++ b/ui/ui-tooling/src/androidTest/java/androidx/ui/tooling/preview/animation/PreviewAnimationClockTest.kt
@@ -228,6 +228,16 @@
assertEquals("Offset", offsetAnimation.label)
}
+ @Test
+ fun callbackCalledWhenSettingClockTime() {
+ var callbackCalledCount = 0
+ val clock = TestPreviewAnimationClock { callbackCalledCount++ }
+ clock.setClockTime(10)
+ clock.setClockTime(20)
+
+ assertEquals(2, callbackCalledCount)
+ }
+
// Sets up a transition animation scenario, going from RotationColor.RC1 to RotationColor.RC3.
private fun setUpRotationColorScenario(): ComposeAnimation {
TransitionAnimation(rotationColorDef, testClock).toState(RotationColor.RC2)
@@ -248,7 +258,8 @@
return composeAnimation
}
- private inner class TestPreviewAnimationClock : PreviewAnimationClock(0) {
+ private class TestPreviewAnimationClock(setClockTimeCallback: () -> Unit = {}) :
+ PreviewAnimationClock(0, setClockTimeCallback) {
lateinit var subscribedAnimation: ComposeAnimation
lateinit var unsubscribedAnimation: ComposeAnimation
var subscribeCount = 0
diff --git a/ui/ui-tooling/src/main/java/androidx/ui/tooling/preview/ComposeViewAdapter.kt b/ui/ui-tooling/src/main/java/androidx/ui/tooling/preview/ComposeViewAdapter.kt
index 49ccf9a..d6ad0c21 100644
--- a/ui/ui-tooling/src/main/java/androidx/ui/tooling/preview/ComposeViewAdapter.kt
+++ b/ui/ui-tooling/src/main/java/androidx/ui/tooling/preview/ComposeViewAdapter.kt
@@ -23,6 +23,7 @@
import android.os.Bundle
import android.util.AttributeSet
import android.util.Log
+import android.view.ViewGroup
import android.widget.FrameLayout
import androidx.annotation.VisibleForTesting
import androidx.compose.animation.TransitionModel
@@ -389,7 +390,12 @@
// Provide a custom clock when animation inspection is enabled, i.e. when a
// valid `animationClockStartTime` is passed. This clock will control the
// animations defined in this `ComposeViewAdapter` from Android Studio.
- clock = PreviewAnimationClock(animationClockStartTime)
+ clock = PreviewAnimationClock(animationClockStartTime) {
+ // Invalidate the ComposeViewAdapter's descendants when setting the clock
+ // time to make sure the Compose Preview will animate when the states are
+ // read inside the draw scope.
+ invalidateDescendants()
+ }
Providers(AnimationClockAmbient provides clock) {
composable()
}
@@ -401,6 +407,17 @@
}
/**
+ * Invalidate the [ViewGroup]'s descendants. This should only be called from the UI thread.
+ */
+ private fun ViewGroup.invalidateDescendants() {
+ for (i in 0 until childCount) {
+ val child = getChildAt(i)
+ // Recursively invalidate descendants
+ (child as? ViewGroup)?.invalidateDescendants() ?: child.invalidate()
+ }
+ }
+
+ /**
* Disposes the Compose elements allocated during [init]
*/
internal fun dispose() {
diff --git a/ui/ui-tooling/src/main/java/androidx/ui/tooling/preview/animation/PreviewAnimationClock.kt b/ui/ui-tooling/src/main/java/androidx/ui/tooling/preview/animation/PreviewAnimationClock.kt
index b06a7d3..b279a91 100644
--- a/ui/ui-tooling/src/main/java/androidx/ui/tooling/preview/animation/PreviewAnimationClock.kt
+++ b/ui/ui-tooling/src/main/java/androidx/ui/tooling/preview/animation/PreviewAnimationClock.kt
@@ -41,7 +41,10 @@
* @suppress
*/
@OptIn(InternalAnimationApi::class)
-internal open class PreviewAnimationClock(private val initialTimeMs: Long = 0L) :
+internal open class PreviewAnimationClock(
+ private val initialTimeMs: Long = 0L,
+ private val setClockTimeCallback: () -> Unit = {}
+) :
AnimationClockObservable {
private val TAG = "PreviewAnimationClock"
@@ -205,6 +208,7 @@
*/
fun setClockTime(animationTimeMs: Long) {
clock.clockTimeMillis = initialTimeMs + animationTimeMs
+ setClockTimeCallback.invoke()
}
fun dispose() {
diff --git a/wear/wear/api/api_lint.ignore b/wear/wear/api/api_lint.ignore
index ea6fb8e..5b5e7d1 100644
--- a/wear/wear/api/api_lint.ignore
+++ b/wear/wear/api/api_lint.ignore
@@ -13,6 +13,10 @@
Inconsistent extra value; expected `androidx.wear.ambient.extra.LOWBIT_AMBIENT`, was `com.google.android.wearable.compat.extra.LOWBIT_AMBIENT`
+CallbackMethodName: androidx.wear.widget.CurvingLayoutCallback#adjustAnchorOffsetXY(android.view.View, float[]):
+ Callback method names must follow the on<Something> style: adjustAnchorOffsetXY
+
+
ForbiddenSuperClass: androidx.wear.activity.ConfirmationActivity:
ConfirmationActivity should not extend `Activity`. Activity subclasses are impossible to compose. Expose a composable API instead.
diff --git a/window/window-extensions/api/1.0.0-alpha02.txt b/window/window-extensions/api/1.0.0-alpha02.txt
index 063ecdb..d747aed 100644
--- a/window/window-extensions/api/1.0.0-alpha02.txt
+++ b/window/window-extensions/api/1.0.0-alpha02.txt
@@ -21,16 +21,16 @@
public interface ExtensionInterface {
method public androidx.window.extensions.ExtensionDeviceState getDeviceState();
- method public androidx.window.extensions.ExtensionWindowLayoutInfo getWindowLayoutInfo(android.os.IBinder);
+ method public androidx.window.extensions.ExtensionWindowLayoutInfo getWindowLayoutInfo(android.content.Context);
method public void onDeviceStateListenersChanged(boolean);
- method public void onWindowLayoutChangeListenerAdded(android.os.IBinder);
- method public void onWindowLayoutChangeListenerRemoved(android.os.IBinder);
+ method public void onWindowLayoutChangeListenerAdded(android.content.Context);
+ method public void onWindowLayoutChangeListenerRemoved(android.content.Context);
method public void setExtensionCallback(androidx.window.extensions.ExtensionInterface.ExtensionCallback);
}
public static interface ExtensionInterface.ExtensionCallback {
method public void onDeviceStateChanged(androidx.window.extensions.ExtensionDeviceState);
- method public void onWindowLayoutChanged(android.os.IBinder, androidx.window.extensions.ExtensionWindowLayoutInfo);
+ method public void onWindowLayoutChanged(android.content.Context, androidx.window.extensions.ExtensionWindowLayoutInfo);
}
public final class ExtensionProvider {
diff --git a/window/window-extensions/api/current.txt b/window/window-extensions/api/current.txt
index 063ecdb..d747aed 100644
--- a/window/window-extensions/api/current.txt
+++ b/window/window-extensions/api/current.txt
@@ -21,16 +21,16 @@
public interface ExtensionInterface {
method public androidx.window.extensions.ExtensionDeviceState getDeviceState();
- method public androidx.window.extensions.ExtensionWindowLayoutInfo getWindowLayoutInfo(android.os.IBinder);
+ method public androidx.window.extensions.ExtensionWindowLayoutInfo getWindowLayoutInfo(android.content.Context);
method public void onDeviceStateListenersChanged(boolean);
- method public void onWindowLayoutChangeListenerAdded(android.os.IBinder);
- method public void onWindowLayoutChangeListenerRemoved(android.os.IBinder);
+ method public void onWindowLayoutChangeListenerAdded(android.content.Context);
+ method public void onWindowLayoutChangeListenerRemoved(android.content.Context);
method public void setExtensionCallback(androidx.window.extensions.ExtensionInterface.ExtensionCallback);
}
public static interface ExtensionInterface.ExtensionCallback {
method public void onDeviceStateChanged(androidx.window.extensions.ExtensionDeviceState);
- method public void onWindowLayoutChanged(android.os.IBinder, androidx.window.extensions.ExtensionWindowLayoutInfo);
+ method public void onWindowLayoutChanged(android.content.Context, androidx.window.extensions.ExtensionWindowLayoutInfo);
}
public final class ExtensionProvider {
diff --git a/window/window-extensions/api/public_plus_experimental_1.0.0-alpha02.txt b/window/window-extensions/api/public_plus_experimental_1.0.0-alpha02.txt
index 063ecdb..d747aed 100644
--- a/window/window-extensions/api/public_plus_experimental_1.0.0-alpha02.txt
+++ b/window/window-extensions/api/public_plus_experimental_1.0.0-alpha02.txt
@@ -21,16 +21,16 @@
public interface ExtensionInterface {
method public androidx.window.extensions.ExtensionDeviceState getDeviceState();
- method public androidx.window.extensions.ExtensionWindowLayoutInfo getWindowLayoutInfo(android.os.IBinder);
+ method public androidx.window.extensions.ExtensionWindowLayoutInfo getWindowLayoutInfo(android.content.Context);
method public void onDeviceStateListenersChanged(boolean);
- method public void onWindowLayoutChangeListenerAdded(android.os.IBinder);
- method public void onWindowLayoutChangeListenerRemoved(android.os.IBinder);
+ method public void onWindowLayoutChangeListenerAdded(android.content.Context);
+ method public void onWindowLayoutChangeListenerRemoved(android.content.Context);
method public void setExtensionCallback(androidx.window.extensions.ExtensionInterface.ExtensionCallback);
}
public static interface ExtensionInterface.ExtensionCallback {
method public void onDeviceStateChanged(androidx.window.extensions.ExtensionDeviceState);
- method public void onWindowLayoutChanged(android.os.IBinder, androidx.window.extensions.ExtensionWindowLayoutInfo);
+ method public void onWindowLayoutChanged(android.content.Context, androidx.window.extensions.ExtensionWindowLayoutInfo);
}
public final class ExtensionProvider {
diff --git a/window/window-extensions/api/public_plus_experimental_current.txt b/window/window-extensions/api/public_plus_experimental_current.txt
index 063ecdb..d747aed 100644
--- a/window/window-extensions/api/public_plus_experimental_current.txt
+++ b/window/window-extensions/api/public_plus_experimental_current.txt
@@ -21,16 +21,16 @@
public interface ExtensionInterface {
method public androidx.window.extensions.ExtensionDeviceState getDeviceState();
- method public androidx.window.extensions.ExtensionWindowLayoutInfo getWindowLayoutInfo(android.os.IBinder);
+ method public androidx.window.extensions.ExtensionWindowLayoutInfo getWindowLayoutInfo(android.content.Context);
method public void onDeviceStateListenersChanged(boolean);
- method public void onWindowLayoutChangeListenerAdded(android.os.IBinder);
- method public void onWindowLayoutChangeListenerRemoved(android.os.IBinder);
+ method public void onWindowLayoutChangeListenerAdded(android.content.Context);
+ method public void onWindowLayoutChangeListenerRemoved(android.content.Context);
method public void setExtensionCallback(androidx.window.extensions.ExtensionInterface.ExtensionCallback);
}
public static interface ExtensionInterface.ExtensionCallback {
method public void onDeviceStateChanged(androidx.window.extensions.ExtensionDeviceState);
- method public void onWindowLayoutChanged(android.os.IBinder, androidx.window.extensions.ExtensionWindowLayoutInfo);
+ method public void onWindowLayoutChanged(android.content.Context, androidx.window.extensions.ExtensionWindowLayoutInfo);
}
public final class ExtensionProvider {
diff --git a/window/window-extensions/api/restricted_1.0.0-alpha02.txt b/window/window-extensions/api/restricted_1.0.0-alpha02.txt
index 063ecdb..d747aed 100644
--- a/window/window-extensions/api/restricted_1.0.0-alpha02.txt
+++ b/window/window-extensions/api/restricted_1.0.0-alpha02.txt
@@ -21,16 +21,16 @@
public interface ExtensionInterface {
method public androidx.window.extensions.ExtensionDeviceState getDeviceState();
- method public androidx.window.extensions.ExtensionWindowLayoutInfo getWindowLayoutInfo(android.os.IBinder);
+ method public androidx.window.extensions.ExtensionWindowLayoutInfo getWindowLayoutInfo(android.content.Context);
method public void onDeviceStateListenersChanged(boolean);
- method public void onWindowLayoutChangeListenerAdded(android.os.IBinder);
- method public void onWindowLayoutChangeListenerRemoved(android.os.IBinder);
+ method public void onWindowLayoutChangeListenerAdded(android.content.Context);
+ method public void onWindowLayoutChangeListenerRemoved(android.content.Context);
method public void setExtensionCallback(androidx.window.extensions.ExtensionInterface.ExtensionCallback);
}
public static interface ExtensionInterface.ExtensionCallback {
method public void onDeviceStateChanged(androidx.window.extensions.ExtensionDeviceState);
- method public void onWindowLayoutChanged(android.os.IBinder, androidx.window.extensions.ExtensionWindowLayoutInfo);
+ method public void onWindowLayoutChanged(android.content.Context, androidx.window.extensions.ExtensionWindowLayoutInfo);
}
public final class ExtensionProvider {
diff --git a/window/window-extensions/api/restricted_current.txt b/window/window-extensions/api/restricted_current.txt
index 063ecdb..d747aed 100644
--- a/window/window-extensions/api/restricted_current.txt
+++ b/window/window-extensions/api/restricted_current.txt
@@ -21,16 +21,16 @@
public interface ExtensionInterface {
method public androidx.window.extensions.ExtensionDeviceState getDeviceState();
- method public androidx.window.extensions.ExtensionWindowLayoutInfo getWindowLayoutInfo(android.os.IBinder);
+ method public androidx.window.extensions.ExtensionWindowLayoutInfo getWindowLayoutInfo(android.content.Context);
method public void onDeviceStateListenersChanged(boolean);
- method public void onWindowLayoutChangeListenerAdded(android.os.IBinder);
- method public void onWindowLayoutChangeListenerRemoved(android.os.IBinder);
+ method public void onWindowLayoutChangeListenerAdded(android.content.Context);
+ method public void onWindowLayoutChangeListenerRemoved(android.content.Context);
method public void setExtensionCallback(androidx.window.extensions.ExtensionInterface.ExtensionCallback);
}
public static interface ExtensionInterface.ExtensionCallback {
method public void onDeviceStateChanged(androidx.window.extensions.ExtensionDeviceState);
- method public void onWindowLayoutChanged(android.os.IBinder, androidx.window.extensions.ExtensionWindowLayoutInfo);
+ method public void onWindowLayoutChanged(android.content.Context, androidx.window.extensions.ExtensionWindowLayoutInfo);
}
public final class ExtensionProvider {
diff --git a/window/window-extensions/src/main/java/androidx/window/extensions/ExtensionInterface.java b/window/window-extensions/src/main/java/androidx/window/extensions/ExtensionInterface.java
index b73ee6c..76a1dfa 100644
--- a/window/window-extensions/src/main/java/androidx/window/extensions/ExtensionInterface.java
+++ b/window/window-extensions/src/main/java/androidx/window/extensions/ExtensionInterface.java
@@ -16,7 +16,7 @@
package androidx.window.extensions;
-import android.os.IBinder;
+import android.content.Context;
import androidx.annotation.NonNull;
@@ -40,19 +40,19 @@
* Gets current information about the display features present within the application window.
*/
@NonNull
- ExtensionWindowLayoutInfo getWindowLayoutInfo(@NonNull IBinder windowToken);
+ ExtensionWindowLayoutInfo getWindowLayoutInfo(@NonNull Context context);
/**
* Notifies extension that a listener for display feature layout changes was registered for the
- * given window token.
+ * given activity or window context.
*/
- void onWindowLayoutChangeListenerAdded(@NonNull IBinder windowToken);
+ void onWindowLayoutChangeListenerAdded(@NonNull Context context);
/**
* Notifies extension that a listener for display feature layout changes was removed for the
- * given window token.
+ * given activity or window context.
*/
- void onWindowLayoutChangeListenerRemoved(@NonNull IBinder windowToken);
+ void onWindowLayoutChangeListenerRemoved(@NonNull Context context);
/**
* Gets current device state.
@@ -80,7 +80,7 @@
/**
* Called by extension when the feature layout inside the window changes.
*/
- void onWindowLayoutChanged(@NonNull IBinder windowToken,
+ void onWindowLayoutChanged(@NonNull Context context,
@NonNull ExtensionWindowLayoutInfo newLayout);
}
}
diff --git a/window/window-extensions/src/main/java/androidx/window/extensions/StubExtension.java b/window/window-extensions/src/main/java/androidx/window/extensions/StubExtension.java
index 2846310..3c4532a 100644
--- a/window/window-extensions/src/main/java/androidx/window/extensions/StubExtension.java
+++ b/window/window-extensions/src/main/java/androidx/window/extensions/StubExtension.java
@@ -16,7 +16,7 @@
package androidx.window.extensions;
-import android.os.IBinder;
+import android.content.Context;
import androidx.annotation.NonNull;
@@ -29,7 +29,7 @@
*/
abstract class StubExtension implements ExtensionInterface {
private ExtensionCallback mExtensionCallback;
- private final Set<IBinder> mWindowLayoutChangeListenerTokens = new HashSet<>();
+ private final Set<Context> mWindowLayoutChangeListenerContexts = new HashSet<>();
private boolean mDeviceStateChangeListenerRegistered;
@Override
@@ -38,14 +38,14 @@
}
@Override
- public void onWindowLayoutChangeListenerAdded(@NonNull IBinder windowToken) {
- mWindowLayoutChangeListenerTokens.add(windowToken);
+ public void onWindowLayoutChangeListenerAdded(@NonNull Context context) {
+ mWindowLayoutChangeListenerContexts.add(context);
onListenersChanged();
}
@Override
- public void onWindowLayoutChangeListenerRemoved(@NonNull IBinder windowToken) {
- mWindowLayoutChangeListenerTokens.remove(windowToken);
+ public void onWindowLayoutChangeListenerRemoved(@NonNull Context context) {
+ mWindowLayoutChangeListenerContexts.remove(context);
onListenersChanged();
}
@@ -61,20 +61,21 @@
}
}
- protected void updateWindowLayout(@NonNull IBinder windowToken,
+ protected void updateWindowLayout(@NonNull Context context,
@NonNull ExtensionWindowLayoutInfo newLayout) {
if (mExtensionCallback != null) {
- mExtensionCallback.onWindowLayoutChanged(windowToken, newLayout);
+ mExtensionCallback.onWindowLayoutChanged(context, newLayout);
}
}
@NonNull
- protected Set<IBinder> getWindowsListeningForLayoutChanges() {
- return mWindowLayoutChangeListenerTokens;
+ protected Set<Context> getWindowsListeningForLayoutChanges() {
+ return mWindowLayoutChangeListenerContexts;
}
protected boolean hasListeners() {
- return !mWindowLayoutChangeListenerTokens.isEmpty() || mDeviceStateChangeListenerRegistered;
+ return !mWindowLayoutChangeListenerContexts.isEmpty()
+ || mDeviceStateChangeListenerRegistered;
}
/** Notification to the OEM level that the registered listeners changed. */
diff --git a/window/window/build.gradle b/window/window/build.gradle
index 33a41d2..6b2d419 100644
--- a/window/window/build.gradle
+++ b/window/window/build.gradle
@@ -41,6 +41,7 @@
dependencies {
implementation("androidx.annotation:annotation:1.1.0")
+ implementation("androidx.collection:collection:1.0.0")
implementation "androidx.core:core:1.2.0"
compileOnly(project(":window:window-extensions"))
diff --git a/window/window/proguard-rules.pro b/window/window/proguard-rules.pro
index c66504b..c0f47e2 100644
--- a/window/window/proguard-rules.pro
+++ b/window/window/proguard-rules.pro
@@ -12,4 +12,11 @@
# See the License for the specific language governing permissions and
# limitations under the License.
--keep class androidx.window.** { *; }
+# Some methods in androidx.window.extensions are accessed through reflection and need to be kept.
+# Failure to do so can cause bugs such as b/157286362. This could be overly broad too and should
+# ideally be trimmed down to only the classes/methods that actually need to be kept. This should
+# be tracked in b/165268619.
+-keep class androidx.window.extensions.** { *; }
+
+# We also neep to keep sidecar.** for the same reason.
+-keep class androidx.window.sidecar.** { *; }
diff --git a/window/window/src/androidTest/java/androidx/window/ExtensionCompatDeviceTest.java b/window/window/src/androidTest/java/androidx/window/ExtensionCompatDeviceTest.java
index b3bdf9a..9eda365 100644
--- a/window/window/src/androidTest/java/androidx/window/ExtensionCompatDeviceTest.java
+++ b/window/window/src/androidTest/java/androidx/window/ExtensionCompatDeviceTest.java
@@ -70,8 +70,8 @@
assertNotNull(windowToken);
ExtensionWindowLayoutInfo extensionWindowLayoutInfo =
- mExtensionCompat.mWindowExtension.getWindowLayoutInfo(windowToken);
- WindowLayoutInfo windowLayoutInfo = mExtensionCompat.getWindowLayoutInfo(windowToken);
+ mExtensionCompat.mWindowExtension.getWindowLayoutInfo(activity);
+ WindowLayoutInfo windowLayoutInfo = mExtensionCompat.getWindowLayoutInfo(activity);
for (int i = 0; i < windowLayoutInfo.getDisplayFeatures().size(); i++) {
DisplayFeature feature = windowLayoutInfo.getDisplayFeatures().get(i);
diff --git a/window/window/src/androidTest/java/androidx/window/ExtensionCompatTest.java b/window/window/src/androidTest/java/androidx/window/ExtensionCompatTest.java
index 5632f76..99b076c 100644
--- a/window/window/src/androidTest/java/androidx/window/ExtensionCompatTest.java
+++ b/window/window/src/androidTest/java/androidx/window/ExtensionCompatTest.java
@@ -23,8 +23,8 @@
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
+import android.app.Activity;
import android.graphics.Rect;
-import android.os.IBinder;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.LargeTest;
@@ -53,12 +53,15 @@
implements CompatTestInterface {
private ExtensionInterface mMockExtensionInterface;
+ private Activity mActivity;
@Before
public void setUp() {
mMockExtensionInterface = mock(ExtensionInterface.class);
mExtensionCompat = new ExtensionCompat(mMockExtensionInterface);
+ mActivity = mock(Activity.class);
+
// Setup mocked extension responses
ExtensionDeviceState defaultDeviceState =
new ExtensionDeviceState(ExtensionDeviceState.POSTURE_HALF_OPENED);
@@ -87,8 +90,7 @@
.thenReturn(infoWithEmptyRect);
// Verify that this feature is skipped.
- WindowLayoutInfo windowLayoutInfo =
- mExtensionCompat.getWindowLayoutInfo(mock(IBinder.class));
+ WindowLayoutInfo windowLayoutInfo = mExtensionCompat.getWindowLayoutInfo(mActivity);
assertEquals(features.size() - 1,
windowLayoutInfo.getDisplayFeatures().size());
@@ -117,7 +119,9 @@
verify(callback).onDeviceStateChanged(deviceStateCaptor.capture());
assertEquals(DeviceState.POSTURE_HALF_OPENED, deviceStateCaptor.getValue().getPosture());
- // Verify that the callback set for extension propagates the window layout callback
+ // Verify that the callback set for extension propagates the window layout callback when
+ // a listener has been registered.
+ mExtensionCompat.onWindowLayoutChangeListenerAdded(mActivity);
Rect bounds = new Rect(1, 2, 3, 4);
ExtensionDisplayFeature extensionDisplayFeature =
new ExtensionDisplayFeature(bounds, ExtensionDisplayFeature.TYPE_HINGE);
@@ -125,13 +129,12 @@
displayFeatures.add(extensionDisplayFeature);
ExtensionWindowLayoutInfo extensionWindowLayoutInfo =
new ExtensionWindowLayoutInfo(displayFeatures);
- IBinder windowToken = mock(IBinder.class);
- extensionCallbackCaptor.getValue().onWindowLayoutChanged(windowToken,
+ extensionCallbackCaptor.getValue().onWindowLayoutChanged(mActivity,
extensionWindowLayoutInfo);
ArgumentCaptor<WindowLayoutInfo> windowLayoutInfoCaptor =
ArgumentCaptor.forClass(WindowLayoutInfo.class);
- verify(callback).onWindowLayoutChanged(eq(windowToken), windowLayoutInfoCaptor.capture());
+ verify(callback).onWindowLayoutChanged(eq(mActivity), windowLayoutInfoCaptor.capture());
WindowLayoutInfo capturedLayout = windowLayoutInfoCaptor.getValue();
assertEquals(1, capturedLayout.getDisplayFeatures().size());
@@ -143,19 +146,16 @@
@Test
@Override
public void testOnWindowLayoutChangeListenerAdded() {
- IBinder windowToken = mock(IBinder.class);
- mExtensionCompat.onWindowLayoutChangeListenerAdded(windowToken);
- verify(mExtensionCompat.mWindowExtension)
- .onWindowLayoutChangeListenerAdded(eq(windowToken));
+ mExtensionCompat.onWindowLayoutChangeListenerAdded(mActivity);
+ verify(mExtensionCompat.mWindowExtension).onWindowLayoutChangeListenerAdded(eq(mActivity));
}
@Test
@Override
public void testOnWindowLayoutChangeListenerRemoved() {
- IBinder windowToken = mock(IBinder.class);
- mExtensionCompat.onWindowLayoutChangeListenerRemoved(windowToken);
+ mExtensionCompat.onWindowLayoutChangeListenerRemoved(mActivity);
verify(mExtensionCompat.mWindowExtension)
- .onWindowLayoutChangeListenerRemoved(eq(windowToken));
+ .onWindowLayoutChangeListenerRemoved(eq(mActivity));
}
@Test
diff --git a/window/window/src/androidTest/java/androidx/window/ExtensionTest.java b/window/window/src/androidTest/java/androidx/window/ExtensionTest.java
index 4c08da3..e757f82 100644
--- a/window/window/src/androidTest/java/androidx/window/ExtensionTest.java
+++ b/window/window/src/androidTest/java/androidx/window/ExtensionTest.java
@@ -25,7 +25,6 @@
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotEquals;
-import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
import static org.junit.Assume.assumeTrue;
@@ -33,7 +32,6 @@
import android.content.Intent;
import android.content.pm.ActivityInfo;
import android.graphics.Rect;
-import android.os.IBinder;
import androidx.test.core.app.ApplicationProvider;
import androidx.test.ext.junit.runners.AndroidJUnit4;
@@ -109,11 +107,9 @@
ExtensionInterfaceCompat extension = initAndVerifyExtension(mContext);
TestActivity activity = mActivityTestRule.launchActivity(new Intent());
- IBinder windowToken = getActivityWindowToken(activity);
- assertNotNull(windowToken);
assertTrue("Layout must happen after launch", activity.waitForLayout());
- WindowLayoutInfo windowLayoutInfo = extension.getWindowLayoutInfo(windowToken);
+ WindowLayoutInfo windowLayoutInfo = extension.getWindowLayoutInfo(activity);
if (windowLayoutInfo.getDisplayFeatures().isEmpty()) {
return;
}
@@ -140,11 +136,9 @@
ExtensionInterfaceCompat extension = initAndVerifyExtension(mContext);
TestActivity activity = mActivityTestRule.launchActivity(new Intent());
- IBinder windowToken = getActivityWindowToken(activity);
- assertNotNull(windowToken);
- extension.onWindowLayoutChangeListenerAdded(windowToken);
- extension.onWindowLayoutChangeListenerRemoved(windowToken);
+ extension.onWindowLayoutChangeListenerAdded(activity);
+ extension.onWindowLayoutChangeListenerRemoved(activity);
}
@Test
@@ -154,14 +148,12 @@
TestConfigChangeHandlingActivity activity =
mConfigHandlingActivityTestRule.launchActivity(new Intent());
- IBinder windowToken = getActivityWindowToken(activity);
- assertNotNull(windowToken);
activity.resetLayoutCounter();
activity.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);
activity.waitForLayout();
- WindowLayoutInfo portraitWindowLayoutInfo = extension.getWindowLayoutInfo(windowToken);
+ WindowLayoutInfo portraitWindowLayoutInfo = extension.getWindowLayoutInfo(activity);
if (portraitWindowLayoutInfo.getDisplayFeatures().isEmpty()) {
// No display feature to compare, finish test early
return;
@@ -172,7 +164,7 @@
assertTrue("Layout must happen after orientation change", activity.waitForLayout());
WindowLayoutInfo landscapeWindowLayoutInfo =
- extension.getWindowLayoutInfo(windowToken);
+ extension.getWindowLayoutInfo(activity);
assertNotEquals(portraitWindowLayoutInfo, landscapeWindowLayoutInfo);
}
@@ -188,11 +180,9 @@
activity.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);
activity = mActivityTestRule.getActivity();
- IBinder windowToken = getActivityWindowToken(activity);
- assertNotNull(windowToken);
activity.waitForLayout();
- WindowLayoutInfo portraitWindowLayoutInfo = extension.getWindowLayoutInfo(windowToken);
+ WindowLayoutInfo portraitWindowLayoutInfo = extension.getWindowLayoutInfo(activity);
if (portraitWindowLayoutInfo.getDisplayFeatures().isEmpty()) {
// No display feature to compare, finish test early
return;
@@ -203,11 +193,9 @@
TestActivity.waitForOnResume();
activity = mActivityTestRule.getActivity();
- windowToken = getActivityWindowToken(activity);
- assertNotNull(windowToken);
activity.waitForLayout();
- WindowLayoutInfo landscapeWindowLayoutInfo = extension.getWindowLayoutInfo(windowToken);
+ WindowLayoutInfo landscapeWindowLayoutInfo = extension.getWindowLayoutInfo(activity);
assertNotEquals(portraitWindowLayoutInfo, landscapeWindowLayoutInfo);
}
diff --git a/window/window/src/androidTest/java/androidx/window/ExtensionWindowBackendTest.java b/window/window/src/androidTest/java/androidx/window/ExtensionWindowBackendTest.java
index bb795c6..2ed5ef6 100644
--- a/window/window/src/androidTest/java/androidx/window/ExtensionWindowBackendTest.java
+++ b/window/window/src/androidTest/java/androidx/window/ExtensionWindowBackendTest.java
@@ -34,7 +34,6 @@
import android.content.Context;
import android.content.Intent;
import android.graphics.Rect;
-import android.os.IBinder;
import androidx.core.util.Consumer;
import androidx.test.core.app.ApplicationProvider;
@@ -117,7 +116,6 @@
WindowLayoutInfo layoutInfo = backend.getWindowLayoutInfo(activity);
assertNotNull(layoutInfo);
assertNotNull(layoutInfo.getDisplayFeatures());
- IBinder windowToken = getActivityWindowToken(activity);
}
@Test
@@ -131,10 +129,9 @@
WindowLayoutInfo layoutInfo = backend.getWindowLayoutInfo(activity);
assertNotNull(layoutInfo);
assertNotNull(layoutInfo.getDisplayFeatures());
- IBinder windowToken = getActivityWindowToken(activity);
- verify(backend.mWindowExtension).getWindowLayoutInfo(eq(windowToken));
+ verify(backend.mWindowExtension).getWindowLayoutInfo(activity);
WindowLayoutInfo initialLastReportedState =
- backend.mLastReportedWindowLayouts.get(windowToken);
+ backend.mLastReportedWindowLayouts.get(activity);
// Verify method without extension
backend.mWindowExtension = null;
@@ -144,7 +141,7 @@
assertTrue(layoutInfo.getDisplayFeatures().isEmpty());
// Verify that last reported state does not change when using the getter
- assertEquals(initialLastReportedState, backend.mLastReportedWindowLayouts.get(windowToken));
+ assertEquals(initialLastReportedState, backend.mLastReportedWindowLayouts.get(activity));
}
@Test
@@ -194,15 +191,13 @@
backend.registerLayoutChangeCallback(activity, Runnable::run, consumer);
assertEquals(1, backend.mWindowLayoutChangeCallbacks.size());
- verify(backend.mWindowExtension).onWindowLayoutChangeListenerAdded(
- eq(getActivityWindowToken(activity)));
+ verify(backend.mWindowExtension).onWindowLayoutChangeListenerAdded(eq(activity));
// Check unregistering the layout change callback
backend.unregisterLayoutChangeCallback(consumer);
assertTrue(backend.mWindowLayoutChangeCallbacks.isEmpty());
- verify(backend.mWindowExtension).onWindowLayoutChangeListenerRemoved(
- eq(getActivityWindowToken(activity)));
+ verify(backend.mWindowExtension).onWindowLayoutChangeListenerRemoved(eq(activity));
}
@Test(expected = IllegalStateException.class)
@@ -231,21 +226,20 @@
// Check that callbacks from the extension are propagated correctly
Consumer<WindowLayoutInfo> consumer = mock(Consumer.class);
TestActivity activity = mActivityTestRule.launchActivity(new Intent());
- IBinder windowToken = getActivityWindowToken(activity);
backend.registerLayoutChangeCallback(activity, Runnable::run, consumer);
WindowLayoutInfo windowLayoutInfo = newTestWindowLayoutInfo();
ExtensionWindowBackend.ExtensionListenerImpl backendListener =
backend.new ExtensionListenerImpl();
- backendListener.onWindowLayoutChanged(windowToken, windowLayoutInfo);
+ backendListener.onWindowLayoutChanged(activity, windowLayoutInfo);
verify(consumer).accept(eq(windowLayoutInfo));
- assertEquals(windowLayoutInfo, backend.mLastReportedWindowLayouts.get(windowToken));
+ assertEquals(windowLayoutInfo, backend.mLastReportedWindowLayouts.get(activity));
// Test that the same value wouldn't be reported again
reset(consumer);
- backendListener.onWindowLayoutChanged(windowToken, windowLayoutInfo);
+ backendListener.onWindowLayoutChanged(activity, windowLayoutInfo);
verify(consumer, never()).accept(any());
}
diff --git a/window/window/src/androidTest/java/androidx/window/SidecarCompatDeviceTest.java b/window/window/src/androidTest/java/androidx/window/SidecarCompatDeviceTest.java
index 73980ba..0f57dbd 100644
--- a/window/window/src/androidTest/java/androidx/window/SidecarCompatDeviceTest.java
+++ b/window/window/src/androidTest/java/androidx/window/SidecarCompatDeviceTest.java
@@ -69,7 +69,7 @@
SidecarWindowLayoutInfo sidecarWindowLayoutInfo =
mSidecarCompat.mSidecar.getWindowLayoutInfo(windowToken);
- WindowLayoutInfo windowLayoutInfo = mSidecarCompat.getWindowLayoutInfo(windowToken);
+ WindowLayoutInfo windowLayoutInfo = mSidecarCompat.getWindowLayoutInfo(activity);
for (int i = 0; i < windowLayoutInfo.getDisplayFeatures().size(); i++) {
DisplayFeature feature = windowLayoutInfo.getDisplayFeatures().get(i);
diff --git a/window/window/src/androidTest/java/androidx/window/SidecarCompatTest.java b/window/window/src/androidTest/java/androidx/window/SidecarCompatTest.java
index 6d1e898..568d6c0 100644
--- a/window/window/src/androidTest/java/androidx/window/SidecarCompatTest.java
+++ b/window/window/src/androidTest/java/androidx/window/SidecarCompatTest.java
@@ -20,12 +20,17 @@
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyZeroInteractions;
import static org.mockito.Mockito.when;
+import android.app.Activity;
import android.graphics.Rect;
import android.os.IBinder;
+import android.view.Window;
+import androidx.test.core.app.ApplicationProvider;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.LargeTest;
import androidx.window.sidecar.SidecarDeviceState;
@@ -51,11 +56,20 @@
@RunWith(AndroidJUnit4.class)
public final class SidecarCompatTest extends SidecarCompatDeviceTest
implements CompatTestInterface {
+ private Activity mActivity;
@Before
public void setUp() {
mSidecarCompat = new SidecarCompat(mock(SidecarInterface.class));
+ mActivity = mock(Activity.class);
+ when(mActivity.getResources())
+ .thenReturn(ApplicationProvider.getApplicationContext().getResources());
+
+ Window window = spy(new TestWindow(mActivity));
+ window.getAttributes().token = mock(IBinder.class);
+ when(mActivity.getWindow()).thenReturn(window);
+
// Setup mocked sidecar responses
SidecarDeviceState defaultDeviceState = new SidecarDeviceState();
defaultDeviceState.posture = SidecarDeviceState.POSTURE_HALF_OPENED;
@@ -76,7 +90,7 @@
public void testGetWindowLayout_featureWithEmptyBounds() {
// Add a feature with an empty bounds to the reported list
SidecarWindowLayoutInfo originalWindowLayoutInfo =
- mSidecarCompat.mSidecar.getWindowLayoutInfo(mock(IBinder.class));
+ mSidecarCompat.mSidecar.getWindowLayoutInfo(getActivityWindowToken(mActivity));
List<SidecarDisplayFeature> sidecarDisplayFeatures =
originalWindowLayoutInfo.displayFeatures;
SidecarDisplayFeature newFeature = new SidecarDisplayFeature();
@@ -84,7 +98,7 @@
sidecarDisplayFeatures.add(newFeature);
// Verify that this feature is skipped.
- WindowLayoutInfo windowLayoutInfo = mSidecarCompat.getWindowLayoutInfo(mock(IBinder.class));
+ WindowLayoutInfo windowLayoutInfo = mSidecarCompat.getWindowLayoutInfo(mActivity);
assertEquals(sidecarDisplayFeatures.size() - 1,
windowLayoutInfo.getDisplayFeatures().size());
@@ -112,7 +126,9 @@
verify(callback).onDeviceStateChanged(deviceStateCaptor.capture());
assertEquals(DeviceState.POSTURE_HALF_OPENED, deviceStateCaptor.getValue().getPosture());
- // Verify that the callback set for sidecar propagates the window layout callback
+ // Verify that the callback set for sidecar propagates the window layout callback when a
+ // window layout changed listener has been added.
+ mSidecarCompat.onWindowLayoutChangeListenerAdded(mActivity);
SidecarDisplayFeature sidecarDisplayFeature = new SidecarDisplayFeature();
sidecarDisplayFeature.setType(SidecarDisplayFeature.TYPE_HINGE);
Rect bounds = new Rect(1, 2, 3, 4);
@@ -120,13 +136,12 @@
SidecarWindowLayoutInfo sidecarWindowLayoutInfo = new SidecarWindowLayoutInfo();
sidecarWindowLayoutInfo.displayFeatures = new ArrayList<>();
sidecarWindowLayoutInfo.displayFeatures.add(sidecarDisplayFeature);
- IBinder windowToken = mock(IBinder.class);
- sidecarCallbackCaptor.getValue().onWindowLayoutChanged(windowToken,
+ sidecarCallbackCaptor.getValue().onWindowLayoutChanged(getActivityWindowToken(mActivity),
sidecarWindowLayoutInfo);
ArgumentCaptor<WindowLayoutInfo> windowLayoutInfoCaptor =
ArgumentCaptor.forClass(WindowLayoutInfo.class);
- verify(callback).onWindowLayoutChanged(eq(windowToken), windowLayoutInfoCaptor.capture());
+ verify(callback).onWindowLayoutChanged(eq(mActivity), windowLayoutInfoCaptor.capture());
WindowLayoutInfo capturedLayout = windowLayoutInfoCaptor.getValue();
assertEquals(1, capturedLayout.getDisplayFeatures().size());
@@ -136,18 +151,46 @@
}
@Test
+ public void testMissingCallToOnWindowLayoutChangedListenerAdded() {
+ ArgumentCaptor<SidecarInterface.SidecarCallback> sidecarCallbackCaptor =
+ ArgumentCaptor.forClass(SidecarInterface.SidecarCallback.class);
+
+ // Verify that the sidecar got the callback set
+ ExtensionInterfaceCompat.ExtensionCallbackInterface callback =
+ mock(ExtensionInterfaceCompat.ExtensionCallbackInterface.class);
+ mSidecarCompat.setExtensionCallback(callback);
+
+ verify(mSidecarCompat.mSidecar).setSidecarCallback(sidecarCallbackCaptor.capture());
+
+ // Verify that the callback set for sidecar propagates the window layout callback when a
+ // window layout changed listener has been added.
+ SidecarDisplayFeature sidecarDisplayFeature = new SidecarDisplayFeature();
+ sidecarDisplayFeature.setType(SidecarDisplayFeature.TYPE_HINGE);
+ Rect bounds = new Rect(1, 2, 3, 4);
+ sidecarDisplayFeature.setRect(bounds);
+ SidecarWindowLayoutInfo sidecarWindowLayoutInfo = new SidecarWindowLayoutInfo();
+ sidecarWindowLayoutInfo.displayFeatures = new ArrayList<>();
+ sidecarWindowLayoutInfo.displayFeatures.add(sidecarDisplayFeature);
+
+ IBinder windowToken = mock(IBinder.class);
+ sidecarCallbackCaptor.getValue().onWindowLayoutChanged(windowToken,
+ sidecarWindowLayoutInfo);
+ verifyZeroInteractions(callback);
+ }
+
+ @Test
@Override
public void testOnWindowLayoutChangeListenerAdded() {
- IBinder windowToken = mock(IBinder.class);
- mSidecarCompat.onWindowLayoutChangeListenerAdded(windowToken);
+ IBinder windowToken = getActivityWindowToken(mActivity);
+ mSidecarCompat.onWindowLayoutChangeListenerAdded(mActivity);
verify(mSidecarCompat.mSidecar).onWindowLayoutChangeListenerAdded(eq(windowToken));
}
@Test
@Override
public void testOnWindowLayoutChangeListenerRemoved() {
- IBinder windowToken = mock(IBinder.class);
- mSidecarCompat.onWindowLayoutChangeListenerRemoved(windowToken);
+ IBinder windowToken = getActivityWindowToken(mActivity);
+ mSidecarCompat.onWindowLayoutChangeListenerRemoved(mActivity);
verify(mSidecarCompat.mSidecar).onWindowLayoutChangeListenerRemoved(eq(windowToken));
}
diff --git a/window/window/src/androidTest/java/androidx/window/TestWindow.java b/window/window/src/androidTest/java/androidx/window/TestWindow.java
new file mode 100644
index 0000000..e83d65a
--- /dev/null
+++ b/window/window/src/androidTest/java/androidx/window/TestWindow.java
@@ -0,0 +1,224 @@
+/*
+ * Copyright 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.window;
+
+import android.content.Context;
+import android.content.res.Configuration;
+import android.graphics.drawable.Drawable;
+import android.net.Uri;
+import android.os.Bundle;
+import android.view.InputQueue;
+import android.view.KeyEvent;
+import android.view.LayoutInflater;
+import android.view.MotionEvent;
+import android.view.SurfaceHolder;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.Window;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+/** Stub implementation of {@link Window} for use in tests. */
+public class TestWindow extends Window {
+ public TestWindow(Context context) {
+ super(context);
+ }
+
+ @Override
+ protected void onActive() {}
+
+ @Override
+ public void setChildDrawable(int i, Drawable drawable) {}
+
+ @Override
+ public void setChildInt(int i, int i1) {}
+
+ @Override
+ public boolean isShortcutKey(int i, KeyEvent keyEvent) {
+ return false;
+ }
+
+ @Override
+ public void setVolumeControlStream(int i) {}
+
+ @Override
+ public int getVolumeControlStream() {
+ return 0;
+ }
+
+ @Override
+ public int getStatusBarColor() {
+ return 0;
+ }
+
+ @Override
+ public void setStatusBarColor(int i) {}
+
+ @Override
+ public int getNavigationBarColor() {
+ return 0;
+ }
+
+ @Override
+ public void setNavigationBarColor(int i) {}
+
+ @Override
+ public void setDecorCaptionShade(int i) {}
+
+ @Override
+ public void setResizingCaptionDrawable(Drawable drawable) {}
+
+ @Override
+ public boolean superDispatchKeyEvent(KeyEvent keyEvent) {
+ return false;
+ }
+
+ @Override
+ public boolean superDispatchKeyShortcutEvent(KeyEvent keyEvent) {
+ return false;
+ }
+
+ @Override
+ public boolean superDispatchTouchEvent(MotionEvent motionEvent) {
+ return false;
+ }
+
+ @Override
+ public boolean superDispatchTrackballEvent(MotionEvent motionEvent) {
+ return false;
+ }
+
+ @Override
+ public boolean superDispatchGenericMotionEvent(MotionEvent motionEvent) {
+ return false;
+ }
+
+ @NonNull
+ @Override
+ public View getDecorView() {
+ return null;
+ }
+
+ @Override
+ public View peekDecorView() {
+ return null;
+ }
+
+ @Override
+ public Bundle saveHierarchyState() {
+ return null;
+ }
+
+ @Override
+ public void restoreHierarchyState(Bundle bundle) {}
+
+ @Override
+ public void takeSurface(SurfaceHolder.Callback2 callback2) {}
+
+ @Override
+ public void takeInputQueue(InputQueue.Callback callback) {}
+
+ @Override
+ public boolean isFloating() {
+ return false;
+ }
+
+ @Override
+ public void setContentView(int i) {}
+
+ @Override
+ public void setContentView(View view) {}
+
+ @Override
+ public void setContentView(View view, ViewGroup.LayoutParams layoutParams) {}
+
+ @Override
+ public void addContentView(View view, ViewGroup.LayoutParams layoutParams) {}
+
+ @Nullable
+ @Override
+ public View getCurrentFocus() {
+ return null;
+ }
+
+ @NonNull
+ @Override
+ public LayoutInflater getLayoutInflater() {
+ return null;
+ }
+
+ @Override
+ public void setTitle(CharSequence charSequence) {}
+
+ @Override
+ public void setTitleColor(int i) {}
+
+ @Override
+ public void openPanel(int i, KeyEvent keyEvent) {}
+
+ @Override
+ public void closePanel(int i) {}
+
+ @Override
+ public void togglePanel(int i, KeyEvent keyEvent) {}
+
+ @Override
+ public void invalidatePanelMenu(int i) {}
+
+ @Override
+ public boolean performPanelShortcut(int i, int i1, KeyEvent keyEvent, int i2) {
+ return false;
+ }
+
+ @Override
+ public boolean performPanelIdentifierAction(int i, int i1, int i2) {
+ return false;
+ }
+
+ @Override
+ public void closeAllPanels() {}
+
+ @Override
+ public boolean performContextMenuIdentifierAction(int i, int i1) {
+ return false;
+ }
+
+ @Override
+ public void onConfigurationChanged(Configuration configuration) {}
+
+ @Override
+ public void setBackgroundDrawable(Drawable drawable) {}
+
+ @Override
+ public void setFeatureDrawableResource(int i, int i1) {}
+
+ @Override
+ public void setFeatureDrawable(int i, Drawable drawable) {}
+
+ @Override
+ public void setFeatureDrawableUri(int i, Uri uri) {}
+
+ @Override
+ public void setFeatureDrawableAlpha(int i, int i1) {}
+
+ @Override
+ public void setFeatureInt(int i, int i1) {}
+
+ @Override
+ public void takeKeyEvents(boolean b) {}
+}
diff --git a/window/window/src/main/java/androidx/window/ExtensionCompat.java b/window/window/src/main/java/androidx/window/ExtensionCompat.java
index 87960fe..6f69729 100644
--- a/window/window/src/main/java/androidx/window/ExtensionCompat.java
+++ b/window/window/src/main/java/androidx/window/ExtensionCompat.java
@@ -70,9 +70,9 @@
@Override
@SuppressLint("SyntheticAccessor")
- public void onWindowLayoutChanged(@NonNull IBinder windowToken,
+ public void onWindowLayoutChanged(@NonNull Context context,
@NonNull ExtensionWindowLayoutInfo newLayout) {
- extensionCallback.onWindowLayoutChanged(windowToken,
+ extensionCallback.onWindowLayoutChanged(context,
windowLayoutInfoFromExtension(newLayout));
}
});
@@ -80,20 +80,20 @@
@NonNull
@Override
- public WindowLayoutInfo getWindowLayoutInfo(@NonNull IBinder windowToken) {
+ public WindowLayoutInfo getWindowLayoutInfo(@NonNull Context context) {
ExtensionWindowLayoutInfo windowLayoutInfo =
- mWindowExtension.getWindowLayoutInfo(windowToken);
+ mWindowExtension.getWindowLayoutInfo(context);
return windowLayoutInfoFromExtension(windowLayoutInfo);
}
@Override
- public void onWindowLayoutChangeListenerAdded(@NonNull IBinder windowToken) {
- mWindowExtension.onWindowLayoutChangeListenerAdded(windowToken);
+ public void onWindowLayoutChangeListenerAdded(@NonNull Context context) {
+ mWindowExtension.onWindowLayoutChangeListenerAdded(context);
}
@Override
- public void onWindowLayoutChangeListenerRemoved(@NonNull IBinder windowToken) {
- mWindowExtension.onWindowLayoutChangeListenerRemoved(windowToken);
+ public void onWindowLayoutChangeListenerRemoved(@NonNull Context context) {
+ mWindowExtension.onWindowLayoutChangeListenerRemoved(context);
}
@NonNull
diff --git a/window/window/src/main/java/androidx/window/ExtensionInterfaceCompat.java b/window/window/src/main/java/androidx/window/ExtensionInterfaceCompat.java
index 5b26fef..ea3f803 100644
--- a/window/window/src/main/java/androidx/window/ExtensionInterfaceCompat.java
+++ b/window/window/src/main/java/androidx/window/ExtensionInterfaceCompat.java
@@ -16,7 +16,7 @@
package androidx.window;
-import android.os.IBinder;
+import android.content.Context;
import androidx.annotation.NonNull;
@@ -41,19 +41,19 @@
* Gets current information about the display features present within the application window.
*/
@NonNull
- WindowLayoutInfo getWindowLayoutInfo(@NonNull IBinder windowToken);
+ WindowLayoutInfo getWindowLayoutInfo(@NonNull Context context);
/**
* Notifies extension that a listener for display feature layout changes was registered for the
- * given window token.
+ * given activity or window context.
*/
- void onWindowLayoutChangeListenerAdded(@NonNull IBinder windowToken);
+ void onWindowLayoutChangeListenerAdded(@NonNull Context context);
/**
* Notifies extension that a listener for display feature layout changes was removed for the
- * given window token.
+ * given activity or window context.
*/
- void onWindowLayoutChangeListenerRemoved(@NonNull IBinder windowToken);
+ void onWindowLayoutChangeListenerRemoved(@NonNull Context context);
/**
* Gets current device state.
@@ -81,7 +81,7 @@
/**
* Called by extension when the feature layout inside the window changes.
*/
- void onWindowLayoutChanged(@NonNull IBinder windowToken,
+ void onWindowLayoutChanged(@NonNull Context context,
@NonNull WindowLayoutInfo newLayout);
}
}
diff --git a/window/window/src/main/java/androidx/window/ExtensionWindowBackend.java b/window/window/src/main/java/androidx/window/ExtensionWindowBackend.java
index c4098b3..3ee6f82 100644
--- a/window/window/src/main/java/androidx/window/ExtensionWindowBackend.java
+++ b/window/window/src/main/java/androidx/window/ExtensionWindowBackend.java
@@ -22,7 +22,6 @@
import android.annotation.SuppressLint;
import android.app.Activity;
import android.content.Context;
-import android.os.IBinder;
import android.util.Log;
import androidx.annotation.GuardedBy;
@@ -74,7 +73,7 @@
/** Window layouts that were last reported through callbacks, used to filter out duplicates. */
@GuardedBy("sLock")
@VisibleForTesting
- final HashMap<IBinder, WindowLayoutInfo> mLastReportedWindowLayouts = new HashMap<>();
+ final HashMap<Context, WindowLayoutInfo> mLastReportedWindowLayouts = new HashMap<>();
private static final String TAG = "WindowServer";
@@ -113,14 +112,11 @@
@Override
public WindowLayoutInfo getWindowLayoutInfo(@NonNull Context context) {
Activity activity = assertActivityContext(context);
- IBinder windowToken = getActivityWindowToken(activity);
- if (windowToken == null) {
- throw new IllegalStateException("Activity does not have a window attached.");
- }
+ assertWindowAttached(activity);
synchronized (sLock) {
WindowLayoutInfo windowLayoutInfo = mWindowExtension != null
- ? mWindowExtension.getWindowLayoutInfo(windowToken) : null;
+ ? mWindowExtension.getWindowLayoutInfo(activity) : null;
return windowLayoutInfo != null
? windowLayoutInfo : new WindowLayoutInfo(new ArrayList<>());
}
@@ -148,27 +144,24 @@
}
Activity activity = assertActivityContext(context);
- IBinder windowToken = getActivityWindowToken(activity);
- if (windowToken == null) {
- throw new IllegalStateException("Activity does not have a window attached.");
- }
+ assertWindowAttached(activity);
// Check if the token was already registered, in case we need to report tracking of a
// new token to the extension.
boolean registeredToken = false;
for (WindowLayoutChangeCallbackWrapper callbackWrapper : mWindowLayoutChangeCallbacks) {
- if (callbackWrapper.mToken.equals(windowToken)) {
+ if (callbackWrapper.mContext.equals(activity)) {
registeredToken = true;
break;
}
}
final WindowLayoutChangeCallbackWrapper callbackWrapper =
- new WindowLayoutChangeCallbackWrapper(windowToken, executor, callback);
+ new WindowLayoutChangeCallbackWrapper(activity, executor, callback);
mWindowLayoutChangeCallbacks.add(callbackWrapper);
if (!registeredToken) {
- // Added the first callback for the token.
- mWindowExtension.onWindowLayoutChangeListenerAdded(windowToken);
+ // Added the first callback for the context.
+ mWindowExtension.onWindowLayoutChangeListenerAdded(activity);
}
}
}
@@ -195,25 +188,25 @@
// Remove the items from the list and notify extension if needed.
mWindowLayoutChangeCallbacks.removeAll(itemsToRemove);
for (WindowLayoutChangeCallbackWrapper callbackWrapper : itemsToRemove) {
- callbackRemovedForToken(callbackWrapper.mToken);
+ callbackRemovedForContext(callbackWrapper.mContext);
}
}
}
/**
- * Checks if there are no more registered callbacks left for the token and inform extension if
+ * Checks if there are no more registered callbacks left for the context and inform extension if
* needed.
*/
@GuardedBy("sLock")
- private void callbackRemovedForToken(IBinder token) {
+ private void callbackRemovedForContext(Context context) {
for (WindowLayoutChangeCallbackWrapper callbackWrapper : mWindowLayoutChangeCallbacks) {
- if (callbackWrapper.mToken.equals(token)) {
+ if (callbackWrapper.mContext.equals(context)) {
// Found a registered callback for token.
return;
}
}
- // No registered callbacks left for token - report to extension.
- mWindowExtension.onWindowLayoutChangeListenerRemoved(token);
+ // No registered callbacks left for context - report to extension.
+ mWindowExtension.onWindowLayoutChangeListenerRemoved(context);
}
@Override
@@ -287,10 +280,10 @@
@Override
@SuppressLint("SyntheticAccessor")
- public void onWindowLayoutChanged(@NonNull IBinder windowToken,
+ public void onWindowLayoutChanged(@NonNull Context context,
@NonNull WindowLayoutInfo newLayout) {
synchronized (sLock) {
- WindowLayoutInfo lastReportedValue = mLastReportedWindowLayouts.get(windowToken);
+ WindowLayoutInfo lastReportedValue = mLastReportedWindowLayouts.get(context);
if (newLayout.equals(lastReportedValue)) {
// Skipping, value already reported
if (DEBUG) {
@@ -298,11 +291,11 @@
}
return;
}
- mLastReportedWindowLayouts.put(windowToken, newLayout);
+ mLastReportedWindowLayouts.put(context, newLayout);
}
for (WindowLayoutChangeCallbackWrapper callbackWrapper : mWindowLayoutChangeCallbacks) {
- if (!callbackWrapper.mToken.equals(windowToken)) {
+ if (!callbackWrapper.mContext.equals(context)) {
continue;
}
@@ -325,23 +318,24 @@
return activity;
}
- @Nullable
- private IBinder getActivityWindowToken(Activity activity) {
- return activity.getWindow() != null ? activity.getWindow().getAttributes().token : null;
+ private static void assertWindowAttached(Activity activity) {
+ if (activity.getWindow() == null || activity.getWindow().getAttributes().token == null) {
+ throw new IllegalStateException("Activity does not have a window attached.");
+ }
}
/**
* Wrapper around {@link Consumer<WindowLayoutInfo>} that also includes the {@link Executor}
- * on which the callback should run and the associated token.
+ * on which the callback should run and the visual context.
*/
private static class WindowLayoutChangeCallbackWrapper {
final Executor mExecutor;
final Consumer<WindowLayoutInfo> mCallback;
- final IBinder mToken;
+ final Context mContext;
- WindowLayoutChangeCallbackWrapper(@NonNull IBinder token, @NonNull Executor executor,
+ WindowLayoutChangeCallbackWrapper(@NonNull Context context, @NonNull Executor executor,
@NonNull Consumer<WindowLayoutInfo> callback) {
- mToken = token;
+ mContext = context;
mExecutor = executor;
mCallback = callback;
}
diff --git a/window/window/src/main/java/androidx/window/SidecarCompat.java b/window/window/src/main/java/androidx/window/SidecarCompat.java
index 7dd9b80..923c89e 100644
--- a/window/window/src/main/java/androidx/window/SidecarCompat.java
+++ b/window/window/src/main/java/androidx/window/SidecarCompat.java
@@ -20,8 +20,10 @@
import static androidx.window.DeviceState.POSTURE_UNKNOWN;
import static androidx.window.ExtensionCompat.DEBUG;
import static androidx.window.Version.VERSION_0_1;
+import static androidx.window.WindowManager.getActivityFromContext;
import android.annotation.SuppressLint;
+import android.app.Activity;
import android.content.Context;
import android.graphics.Rect;
import android.os.IBinder;
@@ -31,6 +33,7 @@
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.VisibleForTesting;
+import androidx.collection.SimpleArrayMap;
import androidx.window.sidecar.SidecarDeviceState;
import androidx.window.sidecar.SidecarDisplayFeature;
import androidx.window.sidecar.SidecarInterface;
@@ -46,6 +49,11 @@
final class SidecarCompat implements ExtensionInterfaceCompat {
private static final String TAG = "SidecarCompat";
+ // Map of active listeners registered with #onWindowLayoutChangeListenerAdded() and not yet
+ // removed by #onWindowLayoutChangeListenerRemoved().
+ protected final SimpleArrayMap<IBinder, Context> mWindowListenerRegisteredContexts =
+ new SimpleArrayMap<>();
+
@VisibleForTesting
final SidecarInterface mSidecar;
@@ -74,7 +82,14 @@
@SuppressLint("SyntheticAccessor")
public void onWindowLayoutChanged(@NonNull IBinder windowToken,
@NonNull SidecarWindowLayoutInfo newLayout) {
- extensionCallback.onWindowLayoutChanged(windowToken,
+ Context context = mWindowListenerRegisteredContexts.get(windowToken);
+ if (context == null) {
+ Log.w(TAG, "Unable to resolve context from window token. Missing a call"
+ + "to #onWindowLayoutChangeListenerAdded()?");
+ return;
+ }
+
+ extensionCallback.onWindowLayoutChanged(context,
windowLayoutInfoFromSidecar(newLayout));
}
});
@@ -82,19 +97,32 @@
@NonNull
@Override
- public WindowLayoutInfo getWindowLayoutInfo(@NonNull IBinder windowToken) {
+ public WindowLayoutInfo getWindowLayoutInfo(@NonNull Context context) {
+ Activity activity = assertActivityContext(context);
+ IBinder windowToken = getActivityWindowToken(activity);
+
SidecarWindowLayoutInfo windowLayoutInfo = mSidecar.getWindowLayoutInfo(windowToken);
return windowLayoutInfoFromSidecar(windowLayoutInfo);
}
@Override
- public void onWindowLayoutChangeListenerAdded(@NonNull IBinder windowToken) {
+ public void onWindowLayoutChangeListenerAdded(@NonNull Context context) {
+ Activity activity = assertActivityContext(context);
+ IBinder windowToken = getActivityWindowToken(activity);
+
+ mWindowListenerRegisteredContexts.put(windowToken, activity);
+
mSidecar.onWindowLayoutChangeListenerAdded(windowToken);
}
@Override
- public void onWindowLayoutChangeListenerRemoved(@NonNull IBinder windowToken) {
+ public void onWindowLayoutChangeListenerRemoved(@NonNull Context context) {
+ Activity activity = assertActivityContext(context);
+ IBinder windowToken = getActivityWindowToken(activity);
+
mSidecar.onWindowLayoutChangeListenerRemoved(windowToken);
+
+ mWindowListenerRegisteredContexts.remove(windowToken);
}
@NonNull
@@ -276,4 +304,18 @@
int posture = postureFromSidecar(sidecarDeviceState);
return new DeviceState(posture);
}
+
+ private Activity assertActivityContext(Context context) {
+ Activity activity = getActivityFromContext(context);
+ if (activity == null) {
+ throw new IllegalArgumentException("Used non-visual Context with WindowManager. "
+ + "Please use an Activity or a ContextWrapper around an Activity instead.");
+ }
+ return activity;
+ }
+
+ @Nullable
+ private IBinder getActivityWindowToken(Activity activity) {
+ return activity.getWindow() != null ? activity.getWindow().getAttributes().token : null;
+ }
}
diff --git a/work/workmanager-inspection/src/androidTest/java/androidx/work/inspection/WorkInfoTest.kt b/work/workmanager-inspection/src/androidTest/java/androidx/work/inspection/WorkInfoTest.kt
index 77afd14..412d88b 100644
--- a/work/workmanager-inspection/src/androidTest/java/androidx/work/inspection/WorkInfoTest.kt
+++ b/work/workmanager-inspection/src/androidTest/java/androidx/work/inspection/WorkInfoTest.kt
@@ -156,8 +156,13 @@
testEnvironment.receiveEvent().let { event ->
val workInfo = event.workAdded.work
- assertThat(workInfo.callStack.framesList[0].fileName)
- .isEqualTo("ContinuationImpl.kt")
+ val topCallStack = workInfo.callStack.framesList[0]
+ assertThat(topCallStack.className)
+ .startsWith("androidx.work.inspection.WorkManagerInspector")
+ assertThat(topCallStack.fileName)
+ .isEqualTo("WorkManagerInspector.kt")
+ assertThat(topCallStack.methodName)
+ .isEqualTo("onEntry")
}
}
diff --git a/work/workmanager-inspection/src/main/java/androidx/work/inspection/WorkManagerInspector.kt b/work/workmanager-inspection/src/main/java/androidx/work/inspection/WorkManagerInspector.kt
index 12bc176..baac4d0 100644
--- a/work/workmanager-inspection/src/main/java/androidx/work/inspection/WorkManagerInspector.kt
+++ b/work/workmanager-inspection/src/main/java/androidx/work/inspection/WorkManagerInspector.kt
@@ -57,7 +57,7 @@
private val workManager: WorkManagerImpl
private val executor = Executors.newSingleThreadExecutor()
- private val stackTraceMap = mutableMapOf<String, Array<StackTraceElement>>()
+ private val stackTraceMap = mutableMapOf<String, List<StackTraceElement>>()
init {
workManager = environment.artTI().findInstances(Application::class.java).first()
@@ -73,7 +73,7 @@
val stackTrace = Throwable().stackTrace
executor.submit {
(obj as? WorkContinuationImpl)?.allIds?.forEach { id ->
- stackTraceMap[id] = stackTrace.prune()
+ stackTraceMap[id] = stackTrace.toList().prune()
}
}
}
@@ -135,15 +135,19 @@
}
/**
- * Prune internal [StackTraceElement]s with classes from work manager libraries.
+ * Prune internal [StackTraceElement]s above [WorkContinuationImpl.enqueue] or from
+ * work manager libraries.
*/
- private fun Array<StackTraceElement>.prune(): Array<StackTraceElement> {
- // Find the first element outside work manager libraries.
- val validIndex = indexOfFirst {
- !it.className.startsWith("androidx.work")
+ private fun List<StackTraceElement>.prune(): List<StackTraceElement> {
+ val entryHookIndex = indexOfFirst {
+ it.className.startsWith("androidx.work.impl.WorkContinuationImpl") &&
+ it.methodName == "enqueue"
}
-
- return toList().subList(validIndex, size).toTypedArray()
+ if (entryHookIndex != -1) {
+ return subList(entryHookIndex + 1, size)
+ .dropWhile { it.className.startsWith("androidx.work") }
+ }
+ return this
}
private fun createWorkInfoProto(id: String): WorkManagerInspectorProtocol.WorkInfo {
diff --git a/work/workmanager-multiprocess/src/androidTest/java/androidx/work/multiprocess/ParcelableWorkContinuationImplTest.kt b/work/workmanager-multiprocess/src/androidTest/java/androidx/work/multiprocess/ParcelableWorkContinuationImplTest.kt
new file mode 100644
index 0000000..456ecfb
--- /dev/null
+++ b/work/workmanager-multiprocess/src/androidTest/java/androidx/work/multiprocess/ParcelableWorkContinuationImplTest.kt
@@ -0,0 +1,201 @@
+/*
+ * Copyright 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.work.multiprocess
+
+import android.content.Context
+import androidx.arch.core.executor.ArchTaskExecutor
+import androidx.arch.core.executor.TaskExecutor
+import androidx.test.core.app.ApplicationProvider
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.MediumTest
+import androidx.work.Configuration
+import androidx.work.ExistingWorkPolicy
+import androidx.work.OneTimeWorkRequest
+import androidx.work.WorkRequest
+import androidx.work.impl.Scheduler
+import androidx.work.impl.WorkContinuationImpl
+import androidx.work.impl.WorkManagerImpl
+import androidx.work.impl.utils.SerialExecutor
+import androidx.work.impl.utils.SynchronousExecutor
+import androidx.work.multiprocess.parcelable.ParcelConverters.marshall
+import androidx.work.multiprocess.parcelable.ParcelConverters.unmarshall
+import androidx.work.multiprocess.parcelable.ParcelableWorkContinuationImpl
+import org.junit.Assert.assertEquals
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mockito.`when`
+import org.mockito.Mockito.mock
+import org.mockito.Mockito.spy
+import java.util.concurrent.Executor
+
+@RunWith(AndroidJUnit4::class)
+class ParcelableWorkContinuationImplTest {
+ private lateinit var context: Context
+ private lateinit var workManager: WorkManagerImpl
+
+ @Before
+ fun setUp() {
+ context = ApplicationProvider.getApplicationContext<Context>()
+ val taskExecutor = object : TaskExecutor() {
+ override fun executeOnDiskIO(runnable: Runnable) {
+ runnable.run()
+ }
+
+ override fun isMainThread(): Boolean {
+ return true
+ }
+
+ override fun postToMainThread(runnable: Runnable) {
+ runnable.run()
+ }
+ }
+ ArchTaskExecutor.getInstance().setDelegate(taskExecutor)
+
+ val scheduler = mock(Scheduler::class.java)
+ val configuration = Configuration.Builder()
+ .setExecutor(SynchronousExecutor())
+ .build()
+
+ workManager = spy(
+ WorkManagerImpl(
+ context,
+ configuration,
+ object : androidx.work.impl.utils.taskexecutor.TaskExecutor {
+ val executor = Executor {
+ it.run()
+ }
+ val serialExecutor = SerialExecutor(executor)
+ override fun postToMainThread(runnable: Runnable) {
+ serialExecutor.execute(runnable)
+ }
+
+ override fun getMainThreadExecutor(): Executor {
+ return serialExecutor
+ }
+
+ override fun executeOnBackgroundThread(runnable: Runnable) {
+ serialExecutor.execute(runnable)
+ }
+
+ override fun getBackgroundExecutor(): SerialExecutor {
+ return serialExecutor
+ }
+ }
+ )
+ )
+ `when`<List<Scheduler>>(workManager.schedulers).thenReturn(listOf(scheduler))
+ WorkManagerImpl.setDelegate(workManager)
+ }
+
+ @Test
+ @MediumTest
+ fun basicContinuationTest() {
+ val first = OneTimeWorkRequest.Builder(TestWorker::class.java).build()
+ val second = OneTimeWorkRequest.Builder(TestWorker::class.java).build()
+ val continuation = workManager.beginWith(listOf(first)).then(second)
+ val parcelable = ParcelableWorkContinuationImpl(continuation as WorkContinuationImpl)
+ assertOn(parcelable)
+ }
+
+ @Test
+ @MediumTest
+ fun continuationTests2() {
+ val first = OneTimeWorkRequest.Builder(TestWorker::class.java).build()
+ val second = OneTimeWorkRequest.Builder(TestWorker::class.java).build()
+ val third = OneTimeWorkRequest.Builder(TestWorker::class.java).build()
+ val continuation = workManager.beginWith(listOf(first, second)).then(third)
+ val parcelable = ParcelableWorkContinuationImpl(continuation as WorkContinuationImpl)
+ assertOn(parcelable)
+ }
+
+ @Test
+ @MediumTest
+ fun continuationTest3() {
+ val first = OneTimeWorkRequest.Builder(TestWorker::class.java).build()
+ val second = OneTimeWorkRequest.Builder(TestWorker::class.java).build()
+ val continuation = workManager.beginUniqueWork(
+ "test", ExistingWorkPolicy.REPLACE, listOf(first)
+ ).then(second)
+ val parcelable = ParcelableWorkContinuationImpl(continuation as WorkContinuationImpl)
+ assertOn(parcelable)
+ }
+
+ @Test
+ @MediumTest
+ fun continuationTest4() {
+ val first = OneTimeWorkRequest.Builder(TestWorker::class.java).build()
+ val second = OneTimeWorkRequest.Builder(TestWorker::class.java).build()
+ val continuation = workManager.beginUniqueWork(
+ "test", ExistingWorkPolicy.REPLACE, listOf(first)
+ ).then(second)
+ val parcelable = ParcelableWorkContinuationImpl(continuation as WorkContinuationImpl)
+ val continuation2 = parcelable.info.toWorkContinuationImpl(workManager)
+ equal(
+ ParcelableWorkContinuationImpl(continuation).info,
+ ParcelableWorkContinuationImpl(continuation2).info
+ )
+ }
+
+ @Test
+ @MediumTest
+ fun combineContinuationTests() {
+ val first = OneTimeWorkRequest.Builder(TestWorker::class.java).build()
+ val second = OneTimeWorkRequest.Builder(TestWorker::class.java).build()
+ val third = OneTimeWorkRequest.Builder(TestWorker::class.java).build()
+ val continuation1 = workManager.beginWith(listOf(first, second)).then(third)
+
+ val fourth = OneTimeWorkRequest.Builder(TestWorker::class.java).build()
+ val fifth = OneTimeWorkRequest.Builder(TestWorker::class.java).build()
+ val sixth = OneTimeWorkRequest.Builder(TestWorker::class.java).build()
+ val continuation2 = workManager.beginWith(listOf(fourth, fifth)).then(sixth)
+
+ val continuation = WorkContinuationImpl.combine(listOf(continuation1, continuation2))
+ val parcelable = ParcelableWorkContinuationImpl(continuation as WorkContinuationImpl)
+ assertOn(parcelable)
+ }
+
+ // Utilities
+
+ private fun assertOn(parcelable: ParcelableWorkContinuationImpl) {
+ val parcelable2 = unmarshall(marshall(parcelable), ParcelableWorkContinuationImpl.CREATOR)
+ equal(parcelable.info, parcelable2.info)
+ }
+
+ private fun equal(
+ first: ParcelableWorkContinuationImpl.WorkContinuationImplInfo,
+ second: ParcelableWorkContinuationImpl.WorkContinuationImplInfo
+ ) {
+ assertEquals(first.name, second.name)
+ assertEquals(first.existingWorkPolicy, second.existingWorkPolicy)
+ assertRequests(first.work, second.work)
+ assertEquals(first.parentInfos?.size, first.parentInfos?.size)
+ first.parentInfos?.forEachIndexed { i, info -> equal(info, second.parentInfos!![i]) }
+ }
+
+ private fun assertRequest(first: WorkRequest, second: WorkRequest) {
+ assertEquals(first.id, second.id)
+ assertEquals(first.workSpec, second.workSpec)
+ assertEquals(first.tags, second.tags)
+ }
+
+ private fun assertRequests(listOne: List<WorkRequest>, listTwo: List<WorkRequest>) {
+ listOne.forEachIndexed { i, workRequest ->
+ assertRequest(workRequest, listTwo[i])
+ }
+ }
+}
diff --git a/work/workmanager-multiprocess/src/main/java/androidx/work/multiprocess/parcelable/ParcelableWorkContinuationImpl.java b/work/workmanager-multiprocess/src/main/java/androidx/work/multiprocess/parcelable/ParcelableWorkContinuationImpl.java
new file mode 100644
index 0000000..809ead4
--- /dev/null
+++ b/work/workmanager-multiprocess/src/main/java/androidx/work/multiprocess/parcelable/ParcelableWorkContinuationImpl.java
@@ -0,0 +1,252 @@
+/*
+ * Copyright 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.work.multiprocess.parcelable;
+
+import static androidx.work.multiprocess.parcelable.ParcelUtils.readBooleanValue;
+import static androidx.work.multiprocess.parcelable.ParcelUtils.writeBooleanValue;
+
+import android.annotation.SuppressLint;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.text.TextUtils;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.annotation.RestrictTo;
+import androidx.work.ExistingWorkPolicy;
+import androidx.work.WorkRequest;
+import androidx.work.impl.WorkContinuationImpl;
+import androidx.work.impl.WorkManagerImpl;
+import androidx.work.impl.WorkRequestHolder;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * {@link androidx.work.impl.WorkContinuationImpl}, but parcelable.
+ *
+ * @hide
+ */
+@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
+@SuppressLint("BanParcelableUsage")
+public class ParcelableWorkContinuationImpl implements Parcelable {
+
+ private static final ExistingWorkPolicy[] sValues = ExistingWorkPolicy.values();
+
+ private WorkContinuationImplInfo mInfo;
+
+ public ParcelableWorkContinuationImpl(@NonNull WorkContinuationImpl continuation) {
+ mInfo = new WorkContinuationImplInfo(continuation);
+ }
+
+ public ParcelableWorkContinuationImpl(@NonNull WorkContinuationImplInfo info) {
+ mInfo = info;
+ }
+
+ @NonNull
+ public WorkContinuationImplInfo getInfo() {
+ return mInfo;
+ }
+
+ @SuppressWarnings("unchecked")
+ protected ParcelableWorkContinuationImpl(@NonNull Parcel parcel) {
+ // name
+ String name = null;
+ boolean hasName = readBooleanValue(parcel);
+ if (hasName) {
+ name = parcel.readString();
+ }
+ // workPolicy
+ int ordinal = parcel.readInt();
+ ExistingWorkPolicy workPolicy = sValues[ordinal];
+ // workRequests
+ int requestSize = parcel.readInt();
+ List<WorkRequestHolder> requests = new ArrayList<>(requestSize);
+ ClassLoader loader = getClass().getClassLoader();
+ for (int i = 0; i < requestSize; i++) {
+ ParcelableWorkRequest parcelledRequest = parcel.readParcelable(loader);
+ // We always serialize to a WorkRequestHolder
+ WorkRequestHolder requestHolder = (WorkRequestHolder) parcelledRequest.getWorkRequest();
+ requests.add(requestHolder);
+ }
+ // parents
+ List<WorkContinuationImplInfo> parents = null;
+ boolean hasParents = readBooleanValue(parcel);
+ if (hasParents) {
+ int parentsSize = parcel.readInt();
+ parents = new ArrayList<>(parentsSize);
+ for (int i = 0; i < parentsSize; i++) {
+ ParcelableWorkContinuationImpl parcelledContinuation =
+ parcel.readParcelable(loader);
+ parents.add(parcelledContinuation.getInfo());
+ }
+ }
+ mInfo = new WorkContinuationImplInfo(name, workPolicy, requests, parents);
+ }
+
+ public static final Creator<ParcelableWorkContinuationImpl> CREATOR =
+ new Creator<ParcelableWorkContinuationImpl>() {
+ @Override
+ public ParcelableWorkContinuationImpl createFromParcel(@NonNull Parcel in) {
+ return new ParcelableWorkContinuationImpl(in);
+ }
+
+ @Override
+ public ParcelableWorkContinuationImpl[] newArray(int size) {
+ return new ParcelableWorkContinuationImpl[size];
+ }
+ };
+
+ @Override
+ public int describeContents() {
+ // No file descriptors being returned.
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(@NonNull Parcel parcel, int flags) {
+ // name
+ String name = mInfo.getName();
+ boolean hasName = !TextUtils.isEmpty(name);
+ writeBooleanValue(parcel, hasName);
+ if (hasName) {
+ parcel.writeString(name);
+ }
+ // workPolicy
+ ExistingWorkPolicy policy = mInfo.getExistingWorkPolicy();
+ parcel.writeInt(policy.ordinal());
+ // workRequests
+ List<? extends WorkRequest> requests = mInfo.getWork();
+ parcel.writeInt(requests.size());
+ if (!requests.isEmpty()) {
+ for (int i = 0; i < requests.size(); i++) {
+ WorkRequest request = requests.get(i);
+ ParcelableWorkRequest parcelledRequest = new ParcelableWorkRequest(request);
+ parcel.writeParcelable(parcelledRequest, flags);
+ }
+ }
+ // parents
+ List<WorkContinuationImplInfo> parents = mInfo.getParentInfos();
+ boolean hasParents = parents != null && !parents.isEmpty();
+ writeBooleanValue(parcel, hasParents);
+ if (hasParents) {
+ parcel.writeInt(parents.size());
+ for (int i = 0; i < parents.size(); i++) {
+ ParcelableWorkContinuationImpl parcelledContinuationImpl =
+ new ParcelableWorkContinuationImpl(parents.get(i));
+ parcel.writeParcelable(parcelledContinuationImpl, flags);
+ }
+ }
+ }
+
+ /**
+ * Holds all the information that needs to be tracked inside a
+ * {@link androidx.work.WorkContinuation}.
+ */
+ public static class WorkContinuationImplInfo {
+ private final String mName;
+ private final ExistingWorkPolicy mWorkPolicy;
+ private final List<? extends WorkRequest> mRequests;
+ private List<WorkContinuationImplInfo> mParents;
+
+ public WorkContinuationImplInfo(@NonNull WorkContinuationImpl continuation) {
+ mName = continuation.getName();
+ mWorkPolicy = continuation.getExistingWorkPolicy();
+ mRequests = continuation.getWork();
+ List<WorkContinuationImpl> continuations = continuation.getParents();
+ mParents = null;
+ if (continuations != null) {
+ mParents = new ArrayList<>(continuations.size());
+ for (WorkContinuationImpl workContinuation : continuations) {
+ mParents.add(new WorkContinuationImplInfo(workContinuation));
+ }
+ }
+ }
+
+ public WorkContinuationImplInfo(
+ @Nullable String name,
+ @NonNull ExistingWorkPolicy workPolicy,
+ @NonNull List<? extends WorkRequest> requests,
+ @Nullable List<WorkContinuationImplInfo> parents) {
+ mName = name;
+ mWorkPolicy = workPolicy;
+ mRequests = requests;
+ mParents = parents;
+ }
+
+ @Nullable
+ public String getName() {
+ return mName;
+ }
+
+ @NonNull
+ public ExistingWorkPolicy getExistingWorkPolicy() {
+ return mWorkPolicy;
+ }
+
+ @NonNull
+ public List<? extends WorkRequest> getWork() {
+ return mRequests;
+ }
+
+ @Nullable
+ public List<WorkContinuationImplInfo> getParentInfos() {
+ return mParents;
+ }
+
+ /**
+ * Converts an instance of {@link WorkContinuationImplInfo} to a
+ * {@link androidx.work.WorkContinuation}.
+ *
+ * @param workManager The {@link WorkManagerImpl} instance.
+ * @return The {@link WorkContinuationImpl} instance
+ */
+ @NonNull
+ public WorkContinuationImpl toWorkContinuationImpl(@NonNull WorkManagerImpl workManager) {
+ return new WorkContinuationImpl(
+ workManager,
+ getName(),
+ getExistingWorkPolicy(),
+ getWork(),
+ parents(workManager, getParentInfos())
+ );
+ }
+
+ @Nullable
+ private static List<WorkContinuationImpl> parents(
+ @NonNull WorkManagerImpl workManager,
+ @Nullable List<WorkContinuationImplInfo> parentInfos) {
+
+ if (parentInfos == null) {
+ return null;
+ }
+
+ List<WorkContinuationImpl> continuations = new ArrayList<>(parentInfos.size());
+ for (WorkContinuationImplInfo info : parentInfos) {
+ continuations.add(
+ new WorkContinuationImpl(
+ workManager,
+ info.getName(),
+ info.getExistingWorkPolicy(),
+ info.getWork(),
+ parents(workManager, info.getParentInfos()))
+ );
+ }
+ return continuations;
+ }
+ }
+}
diff --git a/work/workmanager/api/current.txt b/work/workmanager/api/current.txt
index f0f8565..3fb6321 100644
--- a/work/workmanager/api/current.txt
+++ b/work/workmanager/api/current.txt
@@ -300,16 +300,19 @@
}
public final class WorkQuery {
+ method public java.util.List<java.util.UUID!> getIds();
method public java.util.List<androidx.work.WorkInfo.State!> getStates();
method public java.util.List<java.lang.String!> getTags();
method public java.util.List<java.lang.String!> getUniqueWorkNames();
}
public static final class WorkQuery.Builder {
+ method public androidx.work.WorkQuery.Builder addIds(java.util.List<java.util.UUID!>);
method public androidx.work.WorkQuery.Builder addStates(java.util.List<androidx.work.WorkInfo.State!>);
method public androidx.work.WorkQuery.Builder addTags(java.util.List<java.lang.String!>);
method public androidx.work.WorkQuery.Builder addUniqueWorkNames(java.util.List<java.lang.String!>);
method public androidx.work.WorkQuery build();
+ method public static androidx.work.WorkQuery.Builder fromIds(java.util.List<java.util.UUID!>);
method public static androidx.work.WorkQuery.Builder fromStates(java.util.List<androidx.work.WorkInfo.State!>);
method public static androidx.work.WorkQuery.Builder fromTags(java.util.List<java.lang.String!>);
method public static androidx.work.WorkQuery.Builder fromUniqueWorkNames(java.util.List<java.lang.String!>);
diff --git a/work/workmanager/api/public_plus_experimental_current.txt b/work/workmanager/api/public_plus_experimental_current.txt
index f0f8565..3fb6321 100644
--- a/work/workmanager/api/public_plus_experimental_current.txt
+++ b/work/workmanager/api/public_plus_experimental_current.txt
@@ -300,16 +300,19 @@
}
public final class WorkQuery {
+ method public java.util.List<java.util.UUID!> getIds();
method public java.util.List<androidx.work.WorkInfo.State!> getStates();
method public java.util.List<java.lang.String!> getTags();
method public java.util.List<java.lang.String!> getUniqueWorkNames();
}
public static final class WorkQuery.Builder {
+ method public androidx.work.WorkQuery.Builder addIds(java.util.List<java.util.UUID!>);
method public androidx.work.WorkQuery.Builder addStates(java.util.List<androidx.work.WorkInfo.State!>);
method public androidx.work.WorkQuery.Builder addTags(java.util.List<java.lang.String!>);
method public androidx.work.WorkQuery.Builder addUniqueWorkNames(java.util.List<java.lang.String!>);
method public androidx.work.WorkQuery build();
+ method public static androidx.work.WorkQuery.Builder fromIds(java.util.List<java.util.UUID!>);
method public static androidx.work.WorkQuery.Builder fromStates(java.util.List<androidx.work.WorkInfo.State!>);
method public static androidx.work.WorkQuery.Builder fromTags(java.util.List<java.lang.String!>);
method public static androidx.work.WorkQuery.Builder fromUniqueWorkNames(java.util.List<java.lang.String!>);
diff --git a/work/workmanager/api/restricted_current.txt b/work/workmanager/api/restricted_current.txt
index f0f8565..3fb6321 100644
--- a/work/workmanager/api/restricted_current.txt
+++ b/work/workmanager/api/restricted_current.txt
@@ -300,16 +300,19 @@
}
public final class WorkQuery {
+ method public java.util.List<java.util.UUID!> getIds();
method public java.util.List<androidx.work.WorkInfo.State!> getStates();
method public java.util.List<java.lang.String!> getTags();
method public java.util.List<java.lang.String!> getUniqueWorkNames();
}
public static final class WorkQuery.Builder {
+ method public androidx.work.WorkQuery.Builder addIds(java.util.List<java.util.UUID!>);
method public androidx.work.WorkQuery.Builder addStates(java.util.List<androidx.work.WorkInfo.State!>);
method public androidx.work.WorkQuery.Builder addTags(java.util.List<java.lang.String!>);
method public androidx.work.WorkQuery.Builder addUniqueWorkNames(java.util.List<java.lang.String!>);
method public androidx.work.WorkQuery build();
+ method public static androidx.work.WorkQuery.Builder fromIds(java.util.List<java.util.UUID!>);
method public static androidx.work.WorkQuery.Builder fromStates(java.util.List<androidx.work.WorkInfo.State!>);
method public static androidx.work.WorkQuery.Builder fromTags(java.util.List<java.lang.String!>);
method public static androidx.work.WorkQuery.Builder fromUniqueWorkNames(java.util.List<java.lang.String!>);
diff --git a/work/workmanager/src/androidTest/java/androidx/work/RawWorkInfoDaoTest.kt b/work/workmanager/src/androidTest/java/androidx/work/RawWorkInfoDaoTest.kt
index 0422542..a3bff98 100644
--- a/work/workmanager/src/androidTest/java/androidx/work/RawWorkInfoDaoTest.kt
+++ b/work/workmanager/src/androidTest/java/androidx/work/RawWorkInfoDaoTest.kt
@@ -88,6 +88,27 @@
@Test
@SmallTest
+ fun idsOnlyTest1() {
+ val test = OneTimeWorkRequest.from(TestWorker::class.java)
+ val retry = OneTimeWorkRequest.from(RetryWorker::class.java)
+
+ insertWork(test)
+ insertWork(retry)
+
+ val querySpec = WorkQuery.Builder
+ .fromIds(listOf(test.id))
+ .build()
+
+ val pojos = mDatabase.rawWorkInfoDao().getWorkInfoPojos(
+ RawQueries
+ .workQueryToRawQuery(querySpec)
+ )
+ assertThat(pojos.size, `is`(1))
+ assertThat(pojos[0].id, `is`(test.stringId))
+ }
+
+ @Test
+ @SmallTest
fun tagsOnlyTest1() {
val test = OneTimeWorkRequest.from(TestWorker::class.java)
val retry = OneTimeWorkRequest.from(RetryWorker::class.java)
@@ -172,6 +193,31 @@
@Test
@SmallTest
+ fun idsAndTagsTest1() {
+ val test = OneTimeWorkRequest.from(TestWorker::class.java)
+ val retry = OneTimeWorkRequest.from(RetryWorker::class.java)
+
+ insertWork(test)
+ insertTags(test)
+
+ insertWork(retry)
+ insertTags(retry)
+
+ val querySpec = WorkQuery.Builder
+ .fromIds(listOf(test.id, retry.id))
+ .addTags(listOf(TestWorker::class.java.name))
+ .build()
+
+ val pojos = mDatabase.rawWorkInfoDao().getWorkInfoPojos(
+ RawQueries
+ .workQueryToRawQuery(querySpec)
+ )
+ assertThat(pojos.size, `is`(1))
+ assertThat(pojos[0].id, `is`(test.stringId))
+ }
+
+ @Test
+ @SmallTest
fun statesAndTags1() {
val test = OneTimeWorkRequest.Builder(TestWorker::class.java)
.setInitialState(WorkInfo.State.RUNNING)
@@ -227,6 +273,35 @@
@Test
@SmallTest
+ fun idsAndName() {
+ val test = OneTimeWorkRequest.Builder(TestWorker::class.java)
+ .setInitialState(WorkInfo.State.RUNNING)
+ .build()
+
+ val retry = OneTimeWorkRequest.from(RetryWorker::class.java)
+
+ insertWork(test)
+ insertTags(test)
+
+ insertWork(retry)
+ insertTags(retry)
+ insertName("name", retry)
+
+ val querySpec = WorkQuery.Builder
+ .fromIds(listOf(retry.id, test.id))
+ .addUniqueWorkNames(listOf("name"))
+ .build()
+
+ val pojos = mDatabase.rawWorkInfoDao().getWorkInfoPojos(
+ RawQueries
+ .workQueryToRawQuery(querySpec)
+ )
+ assertThat(pojos.size, `is`(1))
+ assertThat(pojos[0].id, `is`(retry.stringId))
+ }
+
+ @Test
+ @SmallTest
fun statesAndName() {
val test = OneTimeWorkRequest.Builder(TestWorker::class.java)
.setInitialState(WorkInfo.State.RUNNING)
@@ -320,6 +395,44 @@
assertThat(pojos[0].id, `is`(test1.stringId))
}
+ @Test
+ @SmallTest
+ fun idsStatesTagsAndName() {
+ val test1 = OneTimeWorkRequest.Builder(TestWorker::class.java)
+ .setInitialState(WorkInfo.State.ENQUEUED)
+ .build()
+
+ val test2 = OneTimeWorkRequest.Builder(TestWorker::class.java)
+ .setInitialState(WorkInfo.State.RUNNING)
+ .build()
+
+ val retry = OneTimeWorkRequest.from(RetryWorker::class.java)
+
+ insertWork(test1)
+ insertTags(test1)
+ insertName("name", test1)
+
+ insertWork(test2)
+ insertTags(test2)
+
+ insertWork(retry)
+ insertTags(retry)
+
+ val querySpec = WorkQuery.Builder
+ .fromStates(listOf(WorkInfo.State.ENQUEUED))
+ .addIds(listOf(test1.id, test2.id, retry.id))
+ .addTags(listOf(TestWorker::class.java.name, RetryWorker::class.java.name))
+ .addUniqueWorkNames(listOf("name"))
+ .build()
+
+ val pojos = mDatabase.rawWorkInfoDao().getWorkInfoPojos(
+ RawQueries
+ .workQueryToRawQuery(querySpec)
+ )
+ assertThat(pojos.size, `is`(1))
+ assertThat(pojos[0].id, `is`(test1.stringId))
+ }
+
@Test(expected = IllegalArgumentException::class)
@SmallTest
fun invalidWorkQuery() {
diff --git a/work/workmanager/src/androidTest/java/androidx/work/impl/background/systemjob/SystemJobSchedulerTest.java b/work/workmanager/src/androidTest/java/androidx/work/impl/background/systemjob/SystemJobSchedulerTest.java
index 11d7a41..7e80974 100644
--- a/work/workmanager/src/androidTest/java/androidx/work/impl/background/systemjob/SystemJobSchedulerTest.java
+++ b/work/workmanager/src/androidTest/java/androidx/work/impl/background/systemjob/SystemJobSchedulerTest.java
@@ -258,7 +258,7 @@
when(mockContext.getPackageName()).thenReturn(
ApplicationProvider.getApplicationContext().getPackageName());
when(mockContext.getSystemService(Context.JOB_SCHEDULER_SERVICE)).thenReturn(mJobScheduler);
- SystemJobScheduler.cancelInvalidJobs(mockContext);
+ SystemJobScheduler.reconcileJobs(mockContext, mWorkManager);
verify(mJobScheduler).cancel(invalidJob.getId());
verify(mJobScheduler, never()).cancel(validJob.getId());
diff --git a/work/workmanager/src/androidTest/java/androidx/work/impl/constraints/trackers/NetworkStateTrackerTest.java b/work/workmanager/src/androidTest/java/androidx/work/impl/constraints/trackers/NetworkStateTrackerTest.java
index 17e3cdd..38a03b9 100644
--- a/work/workmanager/src/androidTest/java/androidx/work/impl/constraints/trackers/NetworkStateTrackerTest.java
+++ b/work/workmanager/src/androidTest/java/androidx/work/impl/constraints/trackers/NetworkStateTrackerTest.java
@@ -30,6 +30,7 @@
import android.net.Network;
import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.MediumTest;
import androidx.test.filters.SdkSuppress;
import androidx.test.filters.SmallTest;
import androidx.work.impl.constraints.NetworkState;
@@ -116,7 +117,7 @@
}
@Test
- @SmallTest
+ @MediumTest
@SdkSuppress(minSdkVersion = 24)
public void handleSecurityExceptions_whenValidatingNetworkState() {
Network activeNetwork = mock(Network.class);
diff --git a/work/workmanager/src/androidTest/java/androidx/work/impl/utils/ForceStopRunnableTest.java b/work/workmanager/src/androidTest/java/androidx/work/impl/utils/ForceStopRunnableTest.java
index bf7ef30..6d6a455 100644
--- a/work/workmanager/src/androidTest/java/androidx/work/impl/utils/ForceStopRunnableTest.java
+++ b/work/workmanager/src/androidTest/java/androidx/work/impl/utils/ForceStopRunnableTest.java
@@ -34,6 +34,7 @@
import androidx.test.core.app.ApplicationProvider;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.MediumTest;
+import androidx.test.filters.SdkSuppress;
import androidx.work.Configuration;
import androidx.work.InitializationExceptionHandler;
import androidx.work.OneTimeWorkRequest;
@@ -41,6 +42,7 @@
import androidx.work.impl.Scheduler;
import androidx.work.impl.WorkDatabase;
import androidx.work.impl.WorkManagerImpl;
+import androidx.work.impl.model.SystemIdInfo;
import androidx.work.impl.model.WorkSpec;
import androidx.work.worker.TestWorker;
@@ -144,6 +146,24 @@
assertThat(workSpec.id, is(captor.getValue().id));
}
+ @Test
+ @SdkSuppress(minSdkVersion = 23)
+ public void testReconcileJobs() {
+ ForceStopRunnable runnable = spy(mRunnable);
+ when(runnable.shouldRescheduleWorkers()).thenReturn(false);
+ when(runnable.isForceStopped()).thenReturn(false);
+ OneTimeWorkRequest request = new OneTimeWorkRequest.Builder(TestWorker.class)
+ .setInitialState(WorkInfo.State.ENQUEUED)
+ .build();
+ WorkSpec workSpec = request.getWorkSpec();
+ mWorkDatabase.workSpecDao().insertWorkSpec(workSpec);
+ mWorkDatabase.systemIdInfoDao().insertSystemIdInfo(new SystemIdInfo(workSpec.id, 0));
+ runnable.run();
+ ArgumentCaptor<WorkSpec> captor = ArgumentCaptor.forClass(WorkSpec.class);
+ verify(mScheduler, times(1)).schedule(captor.capture());
+ assertThat(workSpec.id, is(captor.getValue().id));
+ }
+
@Test(expected = IllegalStateException.class)
public void test_rethrowForNonRecoverableSqliteExceptions() {
ForceStopRunnable runnable = spy(mRunnable);
diff --git a/work/workmanager/src/main/java/androidx/work/WorkQuery.java b/work/workmanager/src/main/java/androidx/work/WorkQuery.java
index 10930f2..ff237af 100644
--- a/work/workmanager/src/main/java/androidx/work/WorkQuery.java
+++ b/work/workmanager/src/main/java/androidx/work/WorkQuery.java
@@ -22,21 +22,25 @@
import java.util.ArrayList;
import java.util.List;
+import java.util.UUID;
/**
- * A specification for querying {@link WorkRequest}s. This is comprised of 3 components; namely
- * unique work names, tags & work states.
+ * A specification for querying {@link WorkRequest}s. This is comprised of 4 components; namely
+ * ids, unique work names, tags & work states.
* <p>
- * A {@link List} of unique work names, or a {@link List} of {@link WorkRequest} tags, or
- * a {@link List} of {@link WorkInfo.State} can be specified.
+ * A {@link List} of {@link WorkRequest} ids, or a {@link List} of unique work names, or a
+ * {@link List} of {@link WorkRequest} tags, or a {@link List} of {@link WorkInfo.State} can be
+ * specified.
* <p>
* Each component in a {@link WorkQuery} is {@code AND}-ed with the others. Each value in a
* component is {@code OR}-ed.
* <p>
* Example:
- * {@code (name1 OR name2 OR ...) AND (tag1 OR tag2 OR ...) AND (state1 OR state2 OR ...)}
+ * {@code (id1 OR id2 OR ...) AND (name1 OR name2 OR ...) AND (tag1 OR tag2 OR ...) AND (state1
+ * OR state2 OR ...)}
*/
public final class WorkQuery {
+ private final List<UUID> mIds;
private final List<String> mUniqueWorkNames;
private final List<String> mTags;
private final List<WorkInfo.State> mStates;
@@ -44,12 +48,21 @@
// Synthetic access
@SuppressWarnings("WeakerAccess")
WorkQuery(@NonNull Builder builder) {
+ mIds = builder.mIds;
mUniqueWorkNames = builder.mUniqueWorkNames;
mTags = builder.mTags;
mStates = builder.mStates;
}
/**
+ * @return The {@link List} of {@link WorkRequest} ids being queried.
+ */
+ @NonNull
+ public List<UUID> getIds() {
+ return mIds;
+ }
+
+ /**
* @return the {@link List} of unique works name being queried
*/
@NonNull
@@ -79,6 +92,8 @@
*/
public static final class Builder {
// Synthetic access
+ List<UUID> mIds;
+ // Synthetic access
List<String> mUniqueWorkNames;
// Synthetic access
List<String> mTags;
@@ -86,12 +101,27 @@
List<WorkInfo.State> mStates;
private Builder() {
+ mIds = new ArrayList<>();
mUniqueWorkNames = new ArrayList<>();
mTags = new ArrayList<>();
mStates = new ArrayList<>();
}
/**
+ * Creates a {@link WorkQuery.Builder} with a {@link List} of {@link WorkRequest} ids.
+ *
+ * @param ids The {@link List} of {@link WorkRequest} ids.
+ * @return a {@link Builder} instance
+ */
+ @NonNull
+ @SuppressLint("BuilderSetStyle")
+ public static Builder fromIds(@NonNull List<UUID> ids) {
+ Builder builder = new Builder();
+ builder.addIds(ids);
+ return builder;
+ }
+
+ /**
* Creates a {@link WorkQuery.Builder} with a {@link List} of {@code uniqueWorkNames}.
*
* @param uniqueWorkNames The {@link List} of unique work names
@@ -134,6 +164,18 @@
}
/**
+ * Adds a {@link List} of {@link WorkRequest} {@code ids} to the {@link WorkQuery}
+ *
+ * @param ids The {@link List} {@link WorkRequest} {@code ids} to add
+ * @return the instance of the {@link Builder}
+ */
+ @NonNull
+ public Builder addIds(@NonNull List<UUID> ids) {
+ mIds.addAll(ids);
+ return this;
+ }
+
+ /**
* Adds a {@link List} of {@code uniqueWorkNames} to the {@link WorkQuery}
*
* @param uniqueWorkNames The {@link List} of unique work names to add
@@ -176,9 +218,13 @@
*/
@NonNull
public WorkQuery build() {
- if (mUniqueWorkNames.isEmpty() && mTags.isEmpty() && mStates.isEmpty()) {
+ if (mIds.isEmpty()
+ && mUniqueWorkNames.isEmpty()
+ && mTags.isEmpty()
+ && mStates.isEmpty()) {
+
String message =
- "Must specify uniqueNames, tags or states when building a WorkQuery";
+ "Must specify ids, uniqueNames, tags or states when building a WorkQuery";
throw new IllegalArgumentException(message);
}
diff --git a/work/workmanager/src/main/java/androidx/work/impl/WorkContinuationImpl.java b/work/workmanager/src/main/java/androidx/work/impl/WorkContinuationImpl.java
index 9ba6b39..a85b598 100644
--- a/work/workmanager/src/main/java/androidx/work/impl/WorkContinuationImpl.java
+++ b/work/workmanager/src/main/java/androidx/work/impl/WorkContinuationImpl.java
@@ -106,7 +106,7 @@
return mParents;
}
- WorkContinuationImpl(
+ public WorkContinuationImpl(
@NonNull WorkManagerImpl workManagerImpl,
@NonNull List<? extends WorkRequest> work) {
this(
@@ -117,17 +117,17 @@
null);
}
- WorkContinuationImpl(
+ public WorkContinuationImpl(
@NonNull WorkManagerImpl workManagerImpl,
- String name,
- ExistingWorkPolicy existingWorkPolicy,
+ @Nullable String name,
+ @NonNull ExistingWorkPolicy existingWorkPolicy,
@NonNull List<? extends WorkRequest> work) {
this(workManagerImpl, name, existingWorkPolicy, work, null);
}
- WorkContinuationImpl(@NonNull WorkManagerImpl workManagerImpl,
- String name,
- ExistingWorkPolicy existingWorkPolicy,
+ public WorkContinuationImpl(@NonNull WorkManagerImpl workManagerImpl,
+ @Nullable String name,
+ @NonNull ExistingWorkPolicy existingWorkPolicy,
@NonNull List<? extends WorkRequest> work,
@Nullable List<WorkContinuationImpl> parents) {
mWorkManagerImpl = workManagerImpl;
diff --git a/work/workmanager/src/main/java/androidx/work/impl/background/systemjob/SystemJobScheduler.java b/work/workmanager/src/main/java/androidx/work/impl/background/systemjob/SystemJobScheduler.java
index 7ba737e..2428b9e 100644
--- a/work/workmanager/src/main/java/androidx/work/impl/background/systemjob/SystemJobScheduler.java
+++ b/work/workmanager/src/main/java/androidx/work/impl/background/systemjob/SystemJobScheduler.java
@@ -18,6 +18,7 @@
import static android.content.Context.JOB_SCHEDULER_SERVICE;
import static androidx.work.impl.background.systemjob.SystemJobInfoConverter.EXTRA_WORK_SPEC_ID;
+import static androidx.work.impl.model.WorkSpec.SCHEDULE_NOT_REQUESTED_YET;
import android.app.job.JobInfo;
import android.app.job.JobScheduler;
@@ -25,6 +26,7 @@
import android.content.Context;
import android.os.Build;
import android.os.PersistableBundle;
+import android.text.TextUtils;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
@@ -38,11 +40,14 @@
import androidx.work.impl.WorkManagerImpl;
import androidx.work.impl.model.SystemIdInfo;
import androidx.work.impl.model.WorkSpec;
+import androidx.work.impl.model.WorkSpecDao;
import androidx.work.impl.utils.IdGenerator;
import java.util.ArrayList;
+import java.util.HashSet;
import java.util.List;
import java.util.Locale;
+import java.util.Set;
/**
* A class that schedules work using {@link android.app.job.JobScheduler}.
@@ -255,25 +260,72 @@
}
/**
- * Cancels invalid jobs owned by WorkManager. This iterates all the jobs set for our
- * {@link SystemJobService} but with invalid extras. These jobs are invalid (inactionable on
- * our part) but occupy slots in JobScheduler. This method is meant to help mitigate problems
- * like b/134058261, where we have faulty implementations of JobScheduler.
+ * Returns <code>true</code> if the list of jobs in {@link JobScheduler} are out of sync with
+ * what {@link androidx.work.WorkManager} expects to see.
+ * <p>
+ * If {@link JobScheduler} knows about things {@link androidx.work.WorkManager} does not know
+ * know about (or does not care about), cancel them.
+ * <p>
+ * If {@link androidx.work.WorkManager} does not see backing jobs in {@link JobScheduler} for
+ * expected {@link WorkSpec}s, reset the {@code scheduleRequestedAt} bit, so that jobs can be
+ * rescheduled.
*
- * @param context The {@link Context} for the {@link JobScheduler}
+ * @param context The application {@link Context}
+ * @param workManager The {@link WorkManagerImpl} instance
+ * @return <code>true</code> if jobs need to be reconciled.
*/
- public static void cancelInvalidJobs(@NonNull Context context) {
+ public static boolean reconcileJobs(
+ @NonNull Context context,
+ @NonNull WorkManagerImpl workManager) {
+
JobScheduler jobScheduler = (JobScheduler) context.getSystemService(JOB_SCHEDULER_SERVICE);
- if (jobScheduler != null) {
- List<JobInfo> jobs = getPendingJobs(context, jobScheduler);
- if (jobs != null && !jobs.isEmpty()) {
- for (JobInfo jobInfo : jobs) {
- if (getWorkSpecIdFromJobInfo(jobInfo) == null) {
- cancelJobById(jobScheduler, jobInfo.getId());
- }
+ List<JobInfo> jobs = getPendingJobs(context, jobScheduler);
+ List<String> workManagerWorkSpecs =
+ workManager.getWorkDatabase().systemIdInfoDao().getWorkSpecIds();
+
+ int jobSize = jobs != null ? jobs.size() : 0;
+ Set<String> jobSchedulerWorkSpecs = new HashSet<>(jobSize);
+ if (jobs != null && !jobs.isEmpty()) {
+ for (JobInfo jobInfo : jobs) {
+ String workSpecId = getWorkSpecIdFromJobInfo(jobInfo);
+ if (!TextUtils.isEmpty(workSpecId)) {
+ jobSchedulerWorkSpecs.add(workSpecId);
+ } else {
+ // Cancels invalid jobs owned by WorkManager.
+ // These jobs are invalid (in-actionable on our part) but occupy slots in
+ // JobScheduler. This is meant to help mitigate problems like b/134058261,
+ // where we have faulty implementations of JobScheduler.
+ cancelJobById(jobScheduler, jobInfo.getId());
}
}
}
+ boolean needsReconciling = false;
+ for (String workSpecId : workManagerWorkSpecs) {
+ if (!jobSchedulerWorkSpecs.contains(workSpecId)) {
+ Logger.get().debug(TAG, "Reconciling jobs");
+ needsReconciling = true;
+ break;
+ }
+ }
+
+ if (needsReconciling) {
+ WorkDatabase workDatabase = workManager.getWorkDatabase();
+ workDatabase.beginTransaction();
+ try {
+ WorkSpecDao workSpecDao = workDatabase.workSpecDao();
+ for (String workSpecId : workManagerWorkSpecs) {
+ // Mark every WorkSpec instance with SCHEDULE_NOT_REQUESTED_AT = -1
+ // so that it can be picked up by JobScheduler again. This is required
+ // because from WorkManager's perspective this job was actually scheduled
+ // (but subsequently dropped). For this job to be picked up by schedulers
+ // observing scheduling limits this bit needs to be reset.
+ workSpecDao.markWorkSpecScheduled(workSpecId, SCHEDULE_NOT_REQUESTED_YET);
+ }
+ } finally {
+ workDatabase.endTransaction();
+ }
+ }
+ return needsReconciling;
}
@Nullable
diff --git a/work/workmanager/src/main/java/androidx/work/impl/model/SystemIdInfoDao.java b/work/workmanager/src/main/java/androidx/work/impl/model/SystemIdInfoDao.java
index fced5ee..b3a7dfa 100644
--- a/work/workmanager/src/main/java/androidx/work/impl/model/SystemIdInfoDao.java
+++ b/work/workmanager/src/main/java/androidx/work/impl/model/SystemIdInfoDao.java
@@ -24,6 +24,8 @@
import androidx.room.Insert;
import androidx.room.Query;
+import java.util.List;
+
/**
* A Data Access Object for {@link SystemIdInfo}.
*/
@@ -52,4 +54,11 @@
*/
@Query("DELETE FROM SystemIdInfo where work_spec_id=:workSpecId")
void removeSystemIdInfo(@NonNull String workSpecId);
+
+ /**
+ * @return The {@link List} of {@link WorkSpec} ids.
+ */
+ @NonNull
+ @Query("SELECT DISTINCT work_spec_id FROM SystemIdInfo")
+ List<String> getWorkSpecIds();
}
diff --git a/work/workmanager/src/main/java/androidx/work/impl/utils/EnqueueRunnable.java b/work/workmanager/src/main/java/androidx/work/impl/utils/EnqueueRunnable.java
index 31a703f..c1d841d 100644
--- a/work/workmanager/src/main/java/androidx/work/impl/utils/EnqueueRunnable.java
+++ b/work/workmanager/src/main/java/androidx/work/impl/utils/EnqueueRunnable.java
@@ -303,6 +303,13 @@
// Set scheduled times only for work without prerequisites and that are
// not periodic. Dependent work will set their scheduled times when they are
// unblocked.
+
+ // We only set the periodStartTime for OneTimeWorkRequest's here. For
+ // PeriodicWorkRequests the first interval duration is effective immediately, and
+ // WorkerWrapper special cases the first run for a PeriodicWorkRequest. This is
+ // essential because we de-dupe multiple runs of the same PeriodicWorkRequest for a
+ // given interval. JobScheduler has bugs that cause PeriodicWorkRequests to run too
+ // frequently otherwise.
if (!workSpec.isPeriodic()) {
workSpec.periodStartTime = currentTimeMillis;
} else {
diff --git a/work/workmanager/src/main/java/androidx/work/impl/utils/ForceStopRunnable.java b/work/workmanager/src/main/java/androidx/work/impl/utils/ForceStopRunnable.java
index 7ebaa43..1ced31d 100644
--- a/work/workmanager/src/main/java/androidx/work/impl/utils/ForceStopRunnable.java
+++ b/work/workmanager/src/main/java/androidx/work/impl/utils/ForceStopRunnable.java
@@ -162,11 +162,13 @@
*/
@VisibleForTesting
public boolean cleanUp() {
- // Mitigation for faulty implementations of JobScheduler (b/134058261
+ boolean needsReconciling = false;
if (Build.VERSION.SDK_INT >= WorkManagerImpl.MIN_JOB_SCHEDULER_API_LEVEL) {
- SystemJobScheduler.cancelInvalidJobs(mContext);
+ // Mitigation for faulty implementations of JobScheduler (b/134058261) and
+ // Mitigation for a platform bug, which causes jobs to get dropped when binding to
+ // SystemJobService fails.
+ needsReconciling = SystemJobScheduler.reconcileJobs(mContext, mWorkManager);
}
-
// Reset previously unfinished work.
WorkDatabase workDatabase = mWorkManager.getWorkDatabase();
WorkSpecDao workSpecDao = workDatabase.workSpecDao();
@@ -194,7 +196,7 @@
} finally {
workDatabase.endTransaction();
}
- return needsScheduling;
+ return needsScheduling || needsReconciling;
}
/**
diff --git a/work/workmanager/src/main/java/androidx/work/impl/utils/RawQueries.java b/work/workmanager/src/main/java/androidx/work/impl/utils/RawQueries.java
index ed3f715..3237753 100644
--- a/work/workmanager/src/main/java/androidx/work/impl/utils/RawQueries.java
+++ b/work/workmanager/src/main/java/androidx/work/impl/utils/RawQueries.java
@@ -25,6 +25,7 @@
import java.util.ArrayList;
import java.util.List;
+import java.util.UUID;
/**
* A helper to build raw SQLite Queries.
@@ -60,6 +61,20 @@
conjunction = " AND";
}
+ List<UUID> ids = querySpec.getIds();
+ if (!ids.isEmpty()) {
+ List<String> workSpecIds = new ArrayList<>(ids.size());
+ for (UUID id : ids) {
+ workSpecIds.add(id.toString());
+ }
+ builder.append(conjunction)
+ .append(" id IN (");
+ bindings(builder, ids.size());
+ builder.append(")");
+ arguments.addAll(workSpecIds);
+ conjunction = " AND";
+ }
+
List<String> tags = querySpec.getTags();
if (!tags.isEmpty()) {
builder.append(conjunction)