Merge "Give car.app.connection provider a name" into androidx-main
diff --git a/annotation/annotation-experimental-lint/integration-tests/lint-baseline.xml b/annotation/annotation-experimental-lint/integration-tests/lint-baseline.xml
index ff3d158..f99cc28 100644
--- a/annotation/annotation-experimental-lint/integration-tests/lint-baseline.xml
+++ b/annotation/annotation-experimental-lint/integration-tests/lint-baseline.xml
@@ -444,6 +444,61 @@
<issue
id="UnsafeOptInUsageError"
message="This declaration is opt-in and its usage should be marked with `@sample.optin.ExperimentalJavaAnnotation` or `@OptIn(markerClass = sample.optin.ExperimentalJavaAnnotation.class)`"
+ errorLine1=" AnnotatedJavaMembers().field = -1"
+ errorLine2=" ~~~~~">
+ <location
+ file="src/main/java/sample/optin/UseJavaExperimentalFromKt.kt"
+ line="144"
+ column="32"/>
+ </issue>
+
+ <issue
+ id="UnsafeOptInUsageError"
+ message="This declaration is opt-in and its usage should be marked with `@sample.optin.ExperimentalJavaAnnotation` or `@OptIn(markerClass = sample.optin.ExperimentalJavaAnnotation.class)`"
+ errorLine1=" AnnotatedJavaMembers().field = -1"
+ errorLine2=" ~~">
+ <location
+ file="src/main/java/sample/optin/UseJavaExperimentalFromKt.kt"
+ line="144"
+ column="40"/>
+ </issue>
+
+ <issue
+ id="UnsafeOptInUsageError"
+ message="This declaration is opt-in and its usage should be marked with `@sample.optin.ExperimentalJavaAnnotation` or `@OptIn(markerClass = sample.optin.ExperimentalJavaAnnotation.class)`"
+ errorLine1=" val value = AnnotatedJavaMembers().field"
+ errorLine2=" ~~~~~">
+ <location
+ file="src/main/java/sample/optin/UseJavaExperimentalFromKt.kt"
+ line="145"
+ column="44"/>
+ </issue>
+
+ <issue
+ id="UnsafeOptInUsageError"
+ message="This declaration is opt-in and its usage should be marked with `@sample.optin.ExperimentalJavaAnnotation` or `@OptIn(markerClass = sample.optin.ExperimentalJavaAnnotation.class)`"
+ errorLine1=" AnnotatedJavaMembers().fieldWithSetMarker = -1"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~">
+ <location
+ file="src/main/java/sample/optin/UseJavaExperimentalFromKt.kt"
+ line="146"
+ column="32"/>
+ </issue>
+
+ <issue
+ id="UnsafeOptInUsageError"
+ message="This declaration is opt-in and its usage should be marked with `@sample.optin.ExperimentalJavaAnnotation` or `@OptIn(markerClass = sample.optin.ExperimentalJavaAnnotation.class)`"
+ errorLine1=" AnnotatedJavaMembers().fieldWithSetMarker = -1"
+ errorLine2=" ~~">
+ <location
+ file="src/main/java/sample/optin/UseJavaExperimentalFromKt.kt"
+ line="146"
+ column="53"/>
+ </issue>
+
+ <issue
+ id="UnsafeOptInUsageError"
+ message="This declaration is opt-in and its usage should be marked with `@sample.optin.ExperimentalJavaAnnotation` or `@OptIn(markerClass = sample.optin.ExperimentalJavaAnnotation.class)`"
errorLine1=" return stableObject.field;"
errorLine2=" ~~~~~">
<location
@@ -487,6 +542,50 @@
<issue
id="UnsafeOptInUsageError"
+ message="This declaration is opt-in and its usage should be marked with `@sample.optin.ExperimentalJavaAnnotation` or `@OptIn(markerClass = sample.optin.ExperimentalJavaAnnotation.class)`"
+ errorLine1=" new AnnotatedJavaMembers().field = -1;"
+ errorLine2=" ~~~~~">
+ <location
+ file="src/main/java/sample/optin/UseJavaExperimentalMembersFromJava.java"
+ line="59"
+ column="36"/>
+ </issue>
+
+ <issue
+ id="UnsafeOptInUsageError"
+ message="This declaration is opt-in and its usage should be marked with `@sample.optin.ExperimentalJavaAnnotation` or `@OptIn(markerClass = sample.optin.ExperimentalJavaAnnotation.class)`"
+ errorLine1=" new AnnotatedJavaMembers().field = -1;"
+ errorLine2=" ~~">
+ <location
+ file="src/main/java/sample/optin/UseJavaExperimentalMembersFromJava.java"
+ line="59"
+ column="44"/>
+ </issue>
+
+ <issue
+ id="UnsafeOptInUsageError"
+ message="This declaration is opt-in and its usage should be marked with `@sample.optin.ExperimentalJavaAnnotation` or `@OptIn(markerClass = sample.optin.ExperimentalJavaAnnotation.class)`"
+ errorLine1=" int value = new AnnotatedJavaMembers().field;"
+ errorLine2=" ~~~~~">
+ <location
+ file="src/main/java/sample/optin/UseJavaExperimentalMembersFromJava.java"
+ line="60"
+ column="48"/>
+ </issue>
+
+ <issue
+ id="UnsafeOptInUsageError"
+ message="This declaration is opt-in and its usage should be marked with `@sample.optin.ExperimentalJavaAnnotation` or `@OptIn(markerClass = sample.optin.ExperimentalJavaAnnotation.class)`"
+ errorLine1=" new AnnotatedJavaMembers().setFieldWithSetMarker(-1);"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="src/main/java/sample/optin/UseJavaExperimentalMembersFromJava.java"
+ line="61"
+ column="36"/>
+ </issue>
+
+ <issue
+ id="UnsafeOptInUsageError"
message="This declaration is opt-in and its usage should be marked with `@sample.optin.ExperimentalJavaAnnotation2` or `@OptIn(markerClass = sample.optin.ExperimentalJavaAnnotation2.class)`"
errorLine1=" AnnotatedJavaClass2 experimentalObject2 = new AnnotatedJavaClass2();"
errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~">
@@ -804,4 +903,15 @@
column="38"/>
</issue>
+ <issue
+ id="UnsafeOptInUsageError"
+ message="This declaration is opt-in and its usage should be marked with `@sample.optin.ExperimentalKotlinAnnotation` or `@OptIn(markerClass = sample.optin.ExperimentalKotlinAnnotation.class)`"
+ errorLine1=" new AnnotatedKotlinMembers().setFieldWithSetMarker(-1);"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="src/main/java/sample/optin/UseKtExperimentalFromJava.java"
+ line="117"
+ column="38"/>
+ </issue>
+
</issues>
diff --git a/annotation/annotation-experimental-lint/integration-tests/src/main/java/sample/optin/AnnotatedJavaMembers.java b/annotation/annotation-experimental-lint/integration-tests/src/main/java/sample/optin/AnnotatedJavaMembers.java
index 406cb9f..82955e3 100644
--- a/annotation/annotation-experimental-lint/integration-tests/src/main/java/sample/optin/AnnotatedJavaMembers.java
+++ b/annotation/annotation-experimental-lint/integration-tests/src/main/java/sample/optin/AnnotatedJavaMembers.java
@@ -23,16 +23,27 @@
@ExperimentalJavaAnnotation
public static final int FIELD_STATIC = -1;
+ private int mFieldWithSetMarker;
+
@ExperimentalJavaAnnotation
public static int methodStatic() {
return -1;
}
@ExperimentalJavaAnnotation
- public final int field = -1;
+ public int field = -1;
@ExperimentalJavaAnnotation
public int method() {
return -1;
}
+
+ public int getFieldWithSetMarker() {
+ return mFieldWithSetMarker;
+ }
+
+ @ExperimentalJavaAnnotation
+ public void setFieldWithSetMarker(int value) {
+ mFieldWithSetMarker = value;
+ }
}
diff --git a/annotation/annotation-experimental-lint/integration-tests/src/main/java/sample/optin/AnnotatedKotlinMembers.kt b/annotation/annotation-experimental-lint/integration-tests/src/main/java/sample/optin/AnnotatedKotlinMembers.kt
index 79a309e..275b2e0 100644
--- a/annotation/annotation-experimental-lint/integration-tests/src/main/java/sample/optin/AnnotatedKotlinMembers.kt
+++ b/annotation/annotation-experimental-lint/integration-tests/src/main/java/sample/optin/AnnotatedKotlinMembers.kt
@@ -18,7 +18,10 @@
open class AnnotatedKotlinMembers {
@ExperimentalKotlinAnnotation
- val field: Int = -1
+ var field: Int = -1
+
+ @set:ExperimentalKotlinAnnotation
+ var fieldWithSetMarker: Int = -1
@ExperimentalKotlinAnnotation
fun method(): Int {
diff --git a/annotation/annotation-experimental-lint/integration-tests/src/main/java/sample/optin/UseJavaExperimentalFromKt.kt b/annotation/annotation-experimental-lint/integration-tests/src/main/java/sample/optin/UseJavaExperimentalFromKt.kt
index af88dc8..a13ae32 100644
--- a/annotation/annotation-experimental-lint/integration-tests/src/main/java/sample/optin/UseJavaExperimentalFromKt.kt
+++ b/annotation/annotation-experimental-lint/integration-tests/src/main/java/sample/optin/UseJavaExperimentalFromKt.kt
@@ -136,4 +136,15 @@
val experimentalObject = AnnotatedJavaClass()
return experimentalObject.method() + AnnotatedJavaClass2.FIELD_STATIC
}
+
+ /**
+ * Unsafe references to experimental properties.
+ */
+ fun unsafePropertyUsage(): Int {
+ AnnotatedJavaMembers().field = -1
+ val value = AnnotatedJavaMembers().field
+ AnnotatedJavaMembers().fieldWithSetMarker = -1
+ val value2 = AnnotatedJavaMembers().fieldWithSetMarker // safe
+ return value + value2
+ }
}
diff --git a/annotation/annotation-experimental-lint/integration-tests/src/main/java/sample/optin/UseJavaExperimentalMembersFromJava.java b/annotation/annotation-experimental-lint/integration-tests/src/main/java/sample/optin/UseJavaExperimentalMembersFromJava.java
index 1aa316e..3366f72 100644
--- a/annotation/annotation-experimental-lint/integration-tests/src/main/java/sample/optin/UseJavaExperimentalMembersFromJava.java
+++ b/annotation/annotation-experimental-lint/integration-tests/src/main/java/sample/optin/UseJavaExperimentalMembersFromJava.java
@@ -51,4 +51,14 @@
int unsafeExperimentalStaticMethod() {
return AnnotatedJavaMembers.methodStatic();
}
+
+ /**
+ * Unsafe references to experimental properties.
+ */
+ void unsafePropertyUsage() {
+ new AnnotatedJavaMembers().field = -1;
+ int value = new AnnotatedJavaMembers().field;
+ new AnnotatedJavaMembers().setFieldWithSetMarker(-1);
+ int value2 = new AnnotatedJavaMembers().getFieldWithSetMarker(); // safe
+ }
}
diff --git a/annotation/annotation-experimental-lint/integration-tests/src/main/java/sample/optin/UseKtExperimentalFromJava.java b/annotation/annotation-experimental-lint/integration-tests/src/main/java/sample/optin/UseKtExperimentalFromJava.java
index ec165d9..5b68a82 100644
--- a/annotation/annotation-experimental-lint/integration-tests/src/main/java/sample/optin/UseKtExperimentalFromJava.java
+++ b/annotation/annotation-experimental-lint/integration-tests/src/main/java/sample/optin/UseKtExperimentalFromJava.java
@@ -109,6 +109,16 @@
}
/**
+ * Unsafe references to experimental properties.
+ */
+ void unsafePropertyUsage() {
+ new AnnotatedKotlinMembers().setField(-1);
+ int value = new AnnotatedKotlinMembers().getField();
+ new AnnotatedKotlinMembers().setFieldWithSetMarker(-1);
+ int value2 = new AnnotatedKotlinMembers().getFieldWithSetMarker(); // safe
+ }
+
+ /**
* Safe usage due to opting in to experimental annotation.
*/
@OptIn(markerClass = ExperimentalKotlinAnnotation.class)
diff --git a/annotation/annotation-experimental-lint/src/test/kotlin/androidx/annotation/experimental/lint/RequiresOptInDetectorTest.kt b/annotation/annotation-experimental-lint/src/test/kotlin/androidx/annotation/experimental/lint/RequiresOptInDetectorTest.kt
index c618fc3..2bcefd3 100644
--- a/annotation/annotation-experimental-lint/src/test/kotlin/androidx/annotation/experimental/lint/RequiresOptInDetectorTest.kt
+++ b/annotation/annotation-experimental-lint/src/test/kotlin/androidx/annotation/experimental/lint/RequiresOptInDetectorTest.kt
@@ -66,7 +66,19 @@
src/sample/optin/UseJavaExperimentalMembersFromJava.java:52: Error: This declaration is opt-in and its usage should be marked with @sample.optin.ExperimentalJavaAnnotation or @OptIn(markerClass = sample.optin.ExperimentalJavaAnnotation.class) [UnsafeOptInUsageError]
return AnnotatedJavaMembers.methodStatic();
~~~~~~~~~~~~
-4 errors, 0 warnings
+src/sample/optin/UseJavaExperimentalMembersFromJava.java:59: Error: This declaration is opt-in and its usage should be marked with @sample.optin.ExperimentalJavaAnnotation or @OptIn(markerClass = sample.optin.ExperimentalJavaAnnotation.class) [UnsafeOptInUsageError]
+ new AnnotatedJavaMembers().field = -1;
+ ~~~~~
+src/sample/optin/UseJavaExperimentalMembersFromJava.java:59: Error: This declaration is opt-in and its usage should be marked with @sample.optin.ExperimentalJavaAnnotation or @OptIn(markerClass = sample.optin.ExperimentalJavaAnnotation.class) [UnsafeOptInUsageError]
+ new AnnotatedJavaMembers().field = -1;
+ ~~
+src/sample/optin/UseJavaExperimentalMembersFromJava.java:60: Error: This declaration is opt-in and its usage should be marked with @sample.optin.ExperimentalJavaAnnotation or @OptIn(markerClass = sample.optin.ExperimentalJavaAnnotation.class) [UnsafeOptInUsageError]
+ int value = new AnnotatedJavaMembers().field;
+ ~~~~~
+src/sample/optin/UseJavaExperimentalMembersFromJava.java:61: Error: This declaration is opt-in and its usage should be marked with @sample.optin.ExperimentalJavaAnnotation or @OptIn(markerClass = sample.optin.ExperimentalJavaAnnotation.class) [UnsafeOptInUsageError]
+ new AnnotatedJavaMembers().setFieldWithSetMarker(-1);
+ ~~~~~~~~~~~~~~~~~~~~~
+8 errors, 0 warnings
""".trimIndent()
/* ktlint-enable max-line-length */
@@ -179,7 +191,22 @@
src/sample/optin/UseJavaExperimentalFromKt.kt:108: Error: This declaration is opt-in and its usage should be marked with @sample.optin.ExperimentalJavaAnnotation2 or @OptIn(markerClass = sample.optin.ExperimentalJavaAnnotation2.class) [UnsafeOptInUsageError]
return experimentalObject.method() + AnnotatedJavaClass2.FIELD_STATIC
~~~~~~~~~~~~
-11 errors, 0 warnings
+src/sample/optin/UseJavaExperimentalFromKt.kt:144: Error: This declaration is opt-in and its usage should be marked with @sample.optin.ExperimentalJavaAnnotation or @OptIn(markerClass = sample.optin.ExperimentalJavaAnnotation.class) [UnsafeOptInUsageError]
+ AnnotatedJavaMembers().field = -1
+ ~~~~~
+src/sample/optin/UseJavaExperimentalFromKt.kt:144: Error: This declaration is opt-in and its usage should be marked with @sample.optin.ExperimentalJavaAnnotation or @OptIn(markerClass = sample.optin.ExperimentalJavaAnnotation.class) [UnsafeOptInUsageError]
+ AnnotatedJavaMembers().field = -1
+ ~~
+src/sample/optin/UseJavaExperimentalFromKt.kt:145: Error: This declaration is opt-in and its usage should be marked with @sample.optin.ExperimentalJavaAnnotation or @OptIn(markerClass = sample.optin.ExperimentalJavaAnnotation.class) [UnsafeOptInUsageError]
+ val value = AnnotatedJavaMembers().field
+ ~~~~~
+src/sample/optin/UseJavaExperimentalFromKt.kt:146: Error: This declaration is opt-in and its usage should be marked with @sample.optin.ExperimentalJavaAnnotation or @OptIn(markerClass = sample.optin.ExperimentalJavaAnnotation.class) [UnsafeOptInUsageError]
+ AnnotatedJavaMembers().fieldWithSetMarker = -1
+ ~~~~~~~~~~~~~~~~~~
+src/sample/optin/UseJavaExperimentalFromKt.kt:146: Error: This declaration is opt-in and its usage should be marked with @sample.optin.ExperimentalJavaAnnotation or @OptIn(markerClass = sample.optin.ExperimentalJavaAnnotation.class) [UnsafeOptInUsageError]
+ AnnotatedJavaMembers().fieldWithSetMarker = -1
+ ~~
+16 errors, 0 warnings
""".trimIndent()
/* ktlint-enable max-line-length */
@@ -198,6 +225,7 @@
javaSample("sample.optin.UseKtExperimentalFromJava")
)
+ // TODO(b/210881073): Access to annotated property `field` is still not detected.
/* ktlint-disable max-line-length */
val expected = """
src/sample/optin/UseKtExperimentalFromJava.java:28: Error: This declaration is opt-in and its usage should be marked with @sample.optin.ExperimentalKotlinAnnotation or @OptIn(markerClass = sample.optin.ExperimentalKotlinAnnotation.class) [UnsafeOptInUsageError]
@@ -221,7 +249,10 @@
src/sample/optin/UseKtExperimentalFromJava.java:108: Error: This declaration is opt-in and its usage should be marked with @sample.optin.ExperimentalJavaAnnotation or @OptIn(markerClass = sample.optin.ExperimentalJavaAnnotation.class) [UnsafeOptInUsageError]
new AnnotatedKotlinMembers().methodWithJavaMarker();
~~~~~~~~~~~~~~~~~~~~
-7 errors, 0 warnings
+src/sample/optin/UseKtExperimentalFromJava.java:117: Error: This declaration is opt-in and its usage should be marked with @sample.optin.ExperimentalKotlinAnnotation or @OptIn(markerClass = sample.optin.ExperimentalKotlinAnnotation.class) [UnsafeOptInUsageError]
+ new AnnotatedKotlinMembers().setFieldWithSetMarker(-1);
+ ~~~~~~~~~~~~~~~~~~~~~
+8 errors, 0 warnings
""".trimIndent()
/* ktlint-enable max-line-length */
diff --git a/appcompat/appcompat/build.gradle b/appcompat/appcompat/build.gradle
index 9272ef6..23e242e 100644
--- a/appcompat/appcompat/build.gradle
+++ b/appcompat/appcompat/build.gradle
@@ -8,7 +8,7 @@
}
dependencies {
- api(project(":annotation:annotation"))
+ api("androidx.annotation:annotation:1.3.0")
api(project(":core:core"))
implementation(project(":emoji2:emoji2"))
@@ -24,6 +24,9 @@
implementation("androidx.resourceinspection:resourceinspection-annotation:1.0.1")
api("androidx.savedstate:savedstate:1.1.0")
+ // Due to experimental annotations used in core.
+ compileOnly(libs.kotlinStdlib)
+
kapt(project(":resourceinspection:resourceinspection-processor"))
androidTestImplementation(libs.kotlinStdlib)
diff --git a/biometric/biometric/src/main/java/androidx/biometric/BiometricFragment.java b/biometric/biometric/src/main/java/androidx/biometric/BiometricFragment.java
index a46071d..9562de2 100644
--- a/biometric/biometric/src/main/java/androidx/biometric/BiometricFragment.java
+++ b/biometric/biometric/src/main/java/androidx/biometric/BiometricFragment.java
@@ -38,7 +38,6 @@
import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentActivity;
import androidx.fragment.app.FragmentManager;
-import androidx.lifecycle.ViewModelStoreOwner;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@@ -215,7 +214,7 @@
* @return The {@link BiometricViewModel} tied to the host lifecycle.
*/
@Nullable
- BiometricViewModel getViewModel(@Nullable ViewModelStoreOwner owner);
+ BiometricViewModel getViewModel(@Nullable Context hostContext);
/**
* Checks if the current device has hardware sensor support for fingerprint authentication.
@@ -257,8 +256,8 @@
@Override
@Nullable
- public BiometricViewModel getViewModel(@Nullable ViewModelStoreOwner owner) {
- return BiometricPrompt.getViewModel(owner);
+ public BiometricViewModel getViewModel(@Nullable Context hostContext) {
+ return BiometricPrompt.getViewModel(hostContext);
}
@Override
@@ -361,7 +360,7 @@
@Nullable
private BiometricViewModel getViewModel() {
if (mViewModel == null) {
- mViewModel = mInjector.getViewModel(this);
+ mViewModel = mInjector.getViewModel(BiometricPrompt.getHostActivityOrContext(this));
}
return mViewModel;
}
@@ -441,6 +440,13 @@
void authenticate(
@NonNull BiometricPrompt.PromptInfo info,
@Nullable BiometricPrompt.CryptoObject crypto) {
+
+ final Context host = BiometricPrompt.getHostActivityOrContext(this);
+ if (host == null) {
+ Log.e(TAG, "Not launching prompt. Client context was null.");
+ return;
+ }
+
final BiometricViewModel viewModel = getViewModel();
if (viewModel == null) {
Log.e(TAG, "Not launching prompt. View model was null.");
@@ -910,7 +916,7 @@
*/
@RequiresApi(Build.VERSION_CODES.LOLLIPOP)
private void launchConfirmCredentialActivity() {
- final Context host = getContext();
+ final Context host = BiometricPrompt.getHostActivityOrContext(this);
if (host == null) {
Log.e(TAG, "Failed to check device credential. Client context not found.");
return;
@@ -1176,7 +1182,7 @@
* @see DeviceUtils#shouldUseFingerprintForCrypto(Context, String, String)
*/
private boolean isFingerprintDialogNeededForCrypto() {
- final Context host = getContext();
+ final Context host = BiometricPrompt.getHostActivityOrContext(this);
final BiometricViewModel viewModel = getViewModel();
return host != null
&& viewModel != null
diff --git a/biometric/biometric/src/main/java/androidx/biometric/BiometricPrompt.java b/biometric/biometric/src/main/java/androidx/biometric/BiometricPrompt.java
index 8deb16a..3ebe8a6 100644
--- a/biometric/biometric/src/main/java/androidx/biometric/BiometricPrompt.java
+++ b/biometric/biometric/src/main/java/androidx/biometric/BiometricPrompt.java
@@ -17,6 +17,7 @@
package androidx.biometric;
import android.annotation.SuppressLint;
+import android.content.Context;
import android.os.Build;
import android.text.TextUtils;
import android.util.Log;
@@ -802,7 +803,7 @@
}
final FragmentManager fragmentManager = fragment.getChildFragmentManager();
- final BiometricViewModel viewModel = getViewModel(fragment);
+ final BiometricViewModel viewModel = getViewModel(getHostActivityOrContext(fragment));
addObservers(fragment, viewModel);
init(fragmentManager, viewModel, null /* executor */, callback);
}
@@ -883,7 +884,7 @@
}
final FragmentManager fragmentManager = fragment.getChildFragmentManager();
- final BiometricViewModel viewModel = getViewModel(fragment);
+ final BiometricViewModel viewModel = getViewModel(getHostActivityOrContext(fragment));
addObservers(fragment, viewModel);
init(fragmentManager, viewModel, executor, callback);
}
@@ -1017,14 +1018,33 @@
}
/**
- * Gets the biometric view model instance for the given owner, creating one if necessary.
+ * Gets the biometric view model instance for the given context, creating one if necessary.
*
- * @param owner The owner of the view model.
+ * @param context The client context that will (directly or indirectly) host the prompt.
* @return A biometric view model tied to the lifecycle of the given activity.
*/
@Nullable
- static BiometricViewModel getViewModel(@Nullable ViewModelStoreOwner owner) {
- return owner != null ? new ViewModelProvider(owner).get(BiometricViewModel.class) : null;
+ static BiometricViewModel getViewModel(@Nullable Context context) {
+ return context instanceof ViewModelStoreOwner
+ ? new ViewModelProvider((ViewModelStoreOwner) context).get(BiometricViewModel.class)
+ : null;
+ }
+
+ /**
+ * Gets the host Activity or Context the given Fragment.
+ *
+ * @param fragment The fragment.
+ * @return The Activity or Context that hosts the Fragment.
+ */
+ @Nullable
+ static Context getHostActivityOrContext(@NonNull Fragment fragment) {
+ final FragmentActivity activity = fragment.getActivity();
+ if (activity != null) {
+ return activity;
+ } else {
+ // If the host activity is null, return the host context instead
+ return fragment.getContext();
+ }
}
/**
diff --git a/biometric/biometric/src/main/java/androidx/biometric/FingerprintDialogFragment.java b/biometric/biometric/src/main/java/androidx/biometric/FingerprintDialogFragment.java
index d29ab9b..13068b0 100644
--- a/biometric/biometric/src/main/java/androidx/biometric/FingerprintDialogFragment.java
+++ b/biometric/biometric/src/main/java/androidx/biometric/FingerprintDialogFragment.java
@@ -249,12 +249,12 @@
* fragment.
*/
private void connectViewModel() {
- final BiometricViewModel viewModel = BiometricPrompt.getViewModel(this);
- if (viewModel == null) {
+ final Context host = BiometricPrompt.getHostActivityOrContext(this);
+ if (host == null) {
return;
}
- mViewModel = viewModel;
+ mViewModel = BiometricPrompt.getViewModel(host);
mViewModel.getFingerprintDialogState().observe(this, new Observer<Integer>() {
@Override
@@ -358,7 +358,8 @@
*/
private int getThemedColorFor(int attr) {
final Context context = getContext();
- if (context == null) {
+ final Context host = BiometricPrompt.getHostActivityOrContext(this);
+ if (context == null || host == null) {
Log.w(TAG, "Unable to get themed color. Context or activity is null.");
return 0;
}
@@ -366,7 +367,7 @@
TypedValue tv = new TypedValue();
Resources.Theme theme = context.getTheme();
theme.resolveAttribute(attr, tv, true /* resolveRefs */);
- TypedArray arr = context.obtainStyledAttributes(tv.data, new int[] {attr});
+ TypedArray arr = host.obtainStyledAttributes(tv.data, new int[] {attr});
final int color = arr.getColor(0 /* index */, 0 /* defValue */);
arr.recycle();
diff --git a/biometric/biometric/src/test/java/androidx/biometric/BiometricFragmentTest.java b/biometric/biometric/src/test/java/androidx/biometric/BiometricFragmentTest.java
index 2c7af90..a46337f 100644
--- a/biometric/biometric/src/test/java/androidx/biometric/BiometricFragmentTest.java
+++ b/biometric/biometric/src/test/java/androidx/biometric/BiometricFragmentTest.java
@@ -36,7 +36,6 @@
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.RequiresApi;
-import androidx.lifecycle.ViewModelStoreOwner;
import org.junit.Before;
import org.junit.Test;
@@ -262,7 +261,7 @@
@Override
@Nullable
- public BiometricViewModel getViewModel(@Nullable ViewModelStoreOwner owner) {
+ public BiometricViewModel getViewModel(@Nullable Context hostContext) {
return mViewModel;
}
diff --git a/camera/camera-camera2/api/public_plus_experimental_1.1.0-beta02.txt b/camera/camera-camera2/api/public_plus_experimental_1.1.0-beta02.txt
index 583ba10..5c763c6 100644
--- a/camera/camera-camera2/api/public_plus_experimental_1.1.0-beta02.txt
+++ b/camera/camera-camera2/api/public_plus_experimental_1.1.0-beta02.txt
@@ -30,6 +30,7 @@
ctor public Camera2Interop.Extender(androidx.camera.core.ExtendableBuilder<T!>);
method public <ValueT> androidx.camera.camera2.interop.Camera2Interop.Extender<T!> setCaptureRequestOption(android.hardware.camera2.CaptureRequest.Key<ValueT!>, ValueT);
method public androidx.camera.camera2.interop.Camera2Interop.Extender<T!> setDeviceStateCallback(android.hardware.camera2.CameraDevice.StateCallback);
+ method @RequiresApi(28) public androidx.camera.camera2.interop.Camera2Interop.Extender<T!> setPhysicalCameraId(String);
method public androidx.camera.camera2.interop.Camera2Interop.Extender<T!> setSessionCaptureCallback(android.hardware.camera2.CameraCaptureSession.CaptureCallback);
method public androidx.camera.camera2.interop.Camera2Interop.Extender<T!> setSessionStateCallback(android.hardware.camera2.CameraCaptureSession.StateCallback);
}
diff --git a/camera/camera-camera2/api/public_plus_experimental_current.txt b/camera/camera-camera2/api/public_plus_experimental_current.txt
index 583ba10..5c763c6 100644
--- a/camera/camera-camera2/api/public_plus_experimental_current.txt
+++ b/camera/camera-camera2/api/public_plus_experimental_current.txt
@@ -30,6 +30,7 @@
ctor public Camera2Interop.Extender(androidx.camera.core.ExtendableBuilder<T!>);
method public <ValueT> androidx.camera.camera2.interop.Camera2Interop.Extender<T!> setCaptureRequestOption(android.hardware.camera2.CaptureRequest.Key<ValueT!>, ValueT);
method public androidx.camera.camera2.interop.Camera2Interop.Extender<T!> setDeviceStateCallback(android.hardware.camera2.CameraDevice.StateCallback);
+ method @RequiresApi(28) public androidx.camera.camera2.interop.Camera2Interop.Extender<T!> setPhysicalCameraId(String);
method public androidx.camera.camera2.interop.Camera2Interop.Extender<T!> setSessionCaptureCallback(android.hardware.camera2.CameraCaptureSession.CaptureCallback);
method public androidx.camera.camera2.interop.Camera2Interop.Extender<T!> setSessionStateCallback(android.hardware.camera2.CameraCaptureSession.StateCallback);
}
diff --git a/camera/camera-camera2/src/main/java/androidx/camera/camera2/internal/CaptureSession.java b/camera/camera-camera2/src/main/java/androidx/camera/camera2/internal/CaptureSession.java
index 064b0a5..1381745 100644
--- a/camera/camera-camera2/src/main/java/androidx/camera/camera2/internal/CaptureSession.java
+++ b/camera/camera-camera2/src/main/java/androidx/camera/camera2/internal/CaptureSession.java
@@ -311,6 +311,8 @@
OutputConfigurationCompat outputConfiguration =
new OutputConfigurationCompat(surface);
// Set the desired physical camera ID, or null to use the logical stream.
+ // TODO(b/219414502): Configure different streams with different physical
+ // camera IDs.
outputConfiguration.setPhysicalCameraId(
camera2Config.getPhysicalCameraId(null));
outputConfigList.add(outputConfiguration);
diff --git a/camera/camera-camera2/src/main/java/androidx/camera/camera2/interop/Camera2Interop.java b/camera/camera-camera2/src/main/java/androidx/camera/camera2/interop/Camera2Interop.java
index 25df968..c5d82ed 100644
--- a/camera/camera-camera2/src/main/java/androidx/camera/camera2/interop/Camera2Interop.java
+++ b/camera/camera-camera2/src/main/java/androidx/camera/camera2/interop/Camera2Interop.java
@@ -190,15 +190,13 @@
* <p>On API <= 27, the physical camera ID will be ignored since logical camera is not
* supported on these API levels.
*
- * <p>If use cases with different physical camera IDs are bound at the same time, an
+ * <p>Currently it doesn't support binding use cases with different physical camera IDs. If
+ * use cases with different physical camera IDs are bound at the same time, an
* {@link IllegalArgumentException} will be thrown.
*
* @param cameraId The desired camera ID.
* @return The current Extender.
- *
- * @hide
*/
- @RestrictTo(Scope.LIBRARY)
@RequiresApi(28)
@NonNull
public Extender<T> setPhysicalCameraId(@NonNull String cameraId) {
diff --git a/camera/camera-core/src/androidTest/java/androidx/camera/core/impl/DeferrableSurfacesTest.java b/camera/camera-core/src/androidTest/java/androidx/camera/core/impl/DeferrableSurfacesTest.java
index a83af98..013a6ea 100644
--- a/camera/camera-core/src/androidTest/java/androidx/camera/core/impl/DeferrableSurfacesTest.java
+++ b/camera/camera-core/src/androidTest/java/androidx/camera/core/impl/DeferrableSurfacesTest.java
@@ -25,9 +25,11 @@
import android.view.Surface;
import androidx.annotation.NonNull;
+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.concurrent.futures.CallbackToFutureAdapter;
+import androidx.concurrent.futures.ResolvableFuture;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.MediumTest;
import androidx.test.filters.SdkSuppress;
@@ -99,6 +101,28 @@
}
@Test
+ public void surfaceListWithTimeout_cancelReturnedFutureWontCancelDeferrableSurfaces() {
+ DeferrableSurface deferrableSurface = new DeferrableSurface() {
+ private final ListenableFuture<Surface> mSurfaceFuture = ResolvableFuture.create();
+ @NonNull
+ @Override
+ protected ListenableFuture<Surface> provideSurface() {
+ // Return a never complete future.
+ return mSurfaceFuture;
+ }
+ };
+ List<DeferrableSurface> surfaces = Arrays.asList(deferrableSurface);
+ ListenableFuture<List<Surface>> listenableFuture =
+ DeferrableSurfaces.surfaceListWithTimeout(surfaces, false,
+ /*timeout=*/Long.MAX_VALUE, CameraXExecutors.directExecutor(),
+ mScheduledExecutorService);
+
+ listenableFuture.cancel(true);
+
+ assertThat(deferrableSurface.getSurface().isCancelled()).isFalse();
+ }
+
+ @Test
public void tryIncrementAll_canIncrementAllCounts() {
DeferrableSurface fakeSurface0 = getFakeDeferrableSurface();
DeferrableSurface fakeSurface1 = getFakeDeferrableSurface();
diff --git a/camera/camera-core/src/main/java/androidx/camera/core/impl/DeferrableSurfaces.java b/camera/camera-core/src/main/java/androidx/camera/core/impl/DeferrableSurfaces.java
index 6e4789c..aa31954 100644
--- a/camera/camera-core/src/main/java/androidx/camera/core/impl/DeferrableSurfaces.java
+++ b/camera/camera-core/src/main/java/androidx/camera/core/impl/DeferrableSurfaces.java
@@ -63,7 +63,8 @@
List<ListenableFuture<Surface>> listenableFutureSurfaces = new ArrayList<>();
for (DeferrableSurface deferrableSurface : deferrableSurfaces) {
- listenableFutureSurfaces.add(deferrableSurface.getSurface());
+ listenableFutureSurfaces.add(
+ Futures.nonCancellationPropagating(deferrableSurface.getSurface()));
}
return CallbackToFutureAdapter.getFuture(
diff --git a/camera/camera-video/src/main/java/androidx/camera/video/internal/compat/quirk/MediaCodecInfoReportIncorrectInfoQuirk.java b/camera/camera-video/src/main/java/androidx/camera/video/internal/compat/quirk/MediaCodecInfoReportIncorrectInfoQuirk.java
index 52a95ad..1d007fb 100644
--- a/camera/camera-video/src/main/java/androidx/camera/video/internal/compat/quirk/MediaCodecInfoReportIncorrectInfoQuirk.java
+++ b/camera/camera-video/src/main/java/androidx/camera/video/internal/compat/quirk/MediaCodecInfoReportIncorrectInfoQuirk.java
@@ -43,6 +43,7 @@
* See b/192431846#comment3.
*
* <p>Motc C, X650 and LG-X230 have the same problem as Nokia 1. See b/199582287</p>
+ * <p>Positivo Twist 2 Pro have the same problem as Nokia 1. See b/218841498</p>
*
* <p>On Huawei Mate9, {@link CamcorderProfile} indicates it can support resolutions 3840x2160 for
* video codec type {@link android.media.MediaRecorder.VideoEncoder#HEVC}, but the current video
@@ -57,7 +58,8 @@
public class MediaCodecInfoReportIncorrectInfoQuirk implements Quirk {
static boolean load() {
- return isNokia1() || isMotoC() || isX650() || isX230() || isHuaweiMate9();
+ return isNokia1() || isMotoC() || isX650() || isX230() || isHuaweiMate9()
+ || isPositivoTwist2Pro();
}
private static boolean isNokia1() {
@@ -81,9 +83,14 @@
return "Huawei".equalsIgnoreCase(Build.BRAND) && "mha-l29".equalsIgnoreCase(Build.MODEL);
}
+ private static boolean isPositivoTwist2Pro() {
+ return "positivo".equalsIgnoreCase(Build.BRAND) && "twist 2 pro".equalsIgnoreCase(
+ Build.MODEL);
+ }
+
/** Check if problematic MediaFormat info for these candidate devices. */
public boolean isUnSupportMediaCodecInfo(@NonNull MediaFormat mediaFormat) {
- if (isNokia1() || isMotoC() || isX650() || isX230()) {
+ if (isNokia1() || isMotoC() || isX650() || isX230() || isPositivoTwist2Pro()) {
/** Checks if the given mime type is a problematic mime type. */
String mimeType = mediaFormat.getString(MediaFormat.KEY_MIME);
return MediaFormat.MIMETYPE_VIDEO_MPEG4.equals(mimeType);
diff --git a/camera/camera-view/src/main/java/androidx/camera/view/internal/compat/quirk/SurfaceViewStretchedQuirk.java b/camera/camera-view/src/main/java/androidx/camera/view/internal/compat/quirk/SurfaceViewStretchedQuirk.java
index b5995b9..c9dbc5d 100644
--- a/camera/camera-view/src/main/java/androidx/camera/view/internal/compat/quirk/SurfaceViewStretchedQuirk.java
+++ b/camera/camera-view/src/main/java/androidx/camera/view/internal/compat/quirk/SurfaceViewStretchedQuirk.java
@@ -34,13 +34,21 @@
private static final String SAMSUNG = "SAMSUNG";
private static final String GALAXY_Z_FOLD_2 = "F2Q";
private static final String GALAXY_Z_FOLD_3 = "Q2Q";
+ private static final String OPPO = "OPPO";
+ private static final String OPPO_FIND_N = "OP4E75L1";
static boolean load() {
- return SAMSUNG.equalsIgnoreCase(Build.MANUFACTURER) && isFold2OrFold3();
+ return isSamsungFold2OrFold3() || isOppoFoldable();
}
- static boolean isFold2OrFold3() {
- return GALAXY_Z_FOLD_2.equalsIgnoreCase(Build.DEVICE)
- || GALAXY_Z_FOLD_3.equalsIgnoreCase(Build.DEVICE);
+ private static boolean isSamsungFold2OrFold3() {
+ return SAMSUNG.equalsIgnoreCase(Build.MANUFACTURER)
+ && (GALAXY_Z_FOLD_2.equalsIgnoreCase(Build.DEVICE)
+ || GALAXY_Z_FOLD_3.equalsIgnoreCase(Build.DEVICE));
+ }
+
+ private static boolean isOppoFoldable() {
+ return OPPO.equalsIgnoreCase(Build.MANUFACTURER)
+ && OPPO_FIND_N.equalsIgnoreCase(Build.DEVICE);
}
}
diff --git a/camera/camera-view/src/test/java/androidx/camera/view/internal/compat/quirk/SurfaceViewStretchedQuirkTest.java b/camera/camera-view/src/test/java/androidx/camera/view/internal/compat/quirk/SurfaceViewStretchedQuirkTest.java
index 97467b2..f653c37 100644
--- a/camera/camera-view/src/test/java/androidx/camera/view/internal/compat/quirk/SurfaceViewStretchedQuirkTest.java
+++ b/camera/camera-view/src/test/java/androidx/camera/view/internal/compat/quirk/SurfaceViewStretchedQuirkTest.java
@@ -37,18 +37,23 @@
@Test
public void quirkExistsOnSamsungGalaxyZFold2() {
- quirkExistsOnDevice("f2q");
+ quirkExistsOnDevice("Samsung", "f2q");
}
@Test
public void quirkExistsOnSamsungGalaxyZFold3() {
- quirkExistsOnDevice("q2q");
+ quirkExistsOnDevice("Samsung", "q2q");
}
- public void quirkExistsOnDevice(String device) {
+ @Test
+ public void quirkExistsOnOppoFindN() {
+ quirkExistsOnDevice("Oppo", "OP4E75L1");
+ }
+
+ public void quirkExistsOnDevice(String manufacturer, String device) {
// Arrange.
ReflectionHelpers.setStaticField(Build.class, "DEVICE", device);
- ReflectionHelpers.setStaticField(Build.class, "MANUFACTURER", "SAMSUNG");
+ ReflectionHelpers.setStaticField(Build.class, "MANUFACTURER", manufacturer);
// Act.
final SurfaceViewStretchedQuirk quirk = DeviceQuirks.get(SurfaceViewStretchedQuirk.class);
diff --git a/collection/collection/build.gradle b/collection/collection/build.gradle
index 74074bc..f878f54 100644
--- a/collection/collection/build.gradle
+++ b/collection/collection/build.gradle
@@ -14,20 +14,45 @@
* limitations under the License.
*/
+
import androidx.build.Publish
plugins {
id("AndroidXPlugin")
- id("java-library")
- id("kotlin")
+ id("org.jetbrains.kotlin.multiplatform")
}
-dependencies {
- api("androidx.annotation:annotation:1.1.0")
- annotationProcessor(libs.nullaway)
- testImplementation(libs.kotlinStdlib)
- testImplementation(libs.junit)
- testImplementation(libs.truth)
+kotlin {
+ jvm {
+ withJava()
+ }
+
+ sourceSets {
+ commonMain {
+ dependencies {
+ api(libs.kotlinStdlibCommon)
+ }
+ }
+ commonTest {
+ dependencies {
+ implementation(libs.kotlinTestCommon)
+ implementation(libs.kotlinTestAnnotationsCommon)
+ }
+ }
+
+ jvmMain {
+ dependencies {
+ api(libs.kotlinStdlib)
+ api("androidx.annotation:annotation:1.3.0")
+ }
+ }
+ jvmTest {
+ dependencies {
+ implementation(libs.kotlinTestJunit)
+ implementation(libs.truth)
+ }
+ }
+ }
}
androidx {
@@ -36,4 +61,5 @@
mavenGroup = LibraryGroups.COLLECTION
inceptionYear = "2018"
description = "Standalone efficient collections."
+ multiplatform = true
}
diff --git a/collection/collection/lint-baseline.xml b/collection/collection/lint-baseline.xml
index cf9b00a..0982a3f 100644
--- a/collection/collection/lint-baseline.xml
+++ b/collection/collection/lint-baseline.xml
@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
-<issues format="6" by="lint 7.1.0-beta02" type="baseline" client="gradle" dependencies="false" name="AGP (7.1.0-beta02)" variant="all" version="7.1.0-beta02">
+<issues format="6" by="lint 7.3.0-alpha01" type="baseline" client="gradle" dependencies="false" name="AGP (7.3.0-alpha01)" variant="all" version="7.3.0-alpha01">
<issue
id="BanSynchronizedMethods"
@@ -7,7 +7,7 @@
errorLine1=" /**"
errorLine2=" ^">
<location
- file="src/main/java/androidx/collection/LruCache.java"
+ file="src/jvmMain/java/androidx/collection/LruCache.java"
line="284"
column="5"/>
</issue>
@@ -18,7 +18,7 @@
errorLine1=" /**"
errorLine2=" ^">
<location
- file="src/main/java/androidx/collection/LruCache.java"
+ file="src/jvmMain/java/androidx/collection/LruCache.java"
line="293"
column="5"/>
</issue>
@@ -29,7 +29,7 @@
errorLine1=" /**"
errorLine2=" ^">
<location
- file="src/main/java/androidx/collection/LruCache.java"
+ file="src/jvmMain/java/androidx/collection/LruCache.java"
line="302"
column="5"/>
</issue>
@@ -40,7 +40,7 @@
errorLine1=" /**"
errorLine2=" ^">
<location
- file="src/main/java/androidx/collection/LruCache.java"
+ file="src/jvmMain/java/androidx/collection/LruCache.java"
line="310"
column="5"/>
</issue>
@@ -51,7 +51,7 @@
errorLine1=" /**"
errorLine2=" ^">
<location
- file="src/main/java/androidx/collection/LruCache.java"
+ file="src/jvmMain/java/androidx/collection/LruCache.java"
line="318"
column="5"/>
</issue>
@@ -62,7 +62,7 @@
errorLine1=" /**"
errorLine2=" ^">
<location
- file="src/main/java/androidx/collection/LruCache.java"
+ file="src/jvmMain/java/androidx/collection/LruCache.java"
line="325"
column="5"/>
</issue>
@@ -73,7 +73,7 @@
errorLine1=" /**"
errorLine2=" ^">
<location
- file="src/main/java/androidx/collection/LruCache.java"
+ file="src/jvmMain/java/androidx/collection/LruCache.java"
line="332"
column="5"/>
</issue>
@@ -84,7 +84,7 @@
errorLine1=" /**"
errorLine2=" ^">
<location
- file="src/main/java/androidx/collection/LruCache.java"
+ file="src/jvmMain/java/androidx/collection/LruCache.java"
line="339"
column="5"/>
</issue>
@@ -95,150 +95,128 @@
errorLine1=" @Override public synchronized final String toString() {"
errorLine2=" ^">
<location
- file="src/main/java/androidx/collection/LruCache.java"
+ file="src/jvmMain/java/androidx/collection/LruCache.java"
line="347"
column="5"/>
</issue>
<issue
id="UnknownNullness"
- message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://android.github.io/kotlin-guides/interop.html#nullability-annotations"
+ message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://developer.android.com/kotlin/interop#nullability_annotations"
errorLine1=" public ArrayMap(SimpleArrayMap map) {"
errorLine2=" ~~~~~~~~~~~~~~">
<location
- file="src/main/java/androidx/collection/ArrayMap.java"
+ file="src/jvmMain/java/androidx/collection/ArrayMap.java"
line="77"
column="21"/>
</issue>
<issue
id="UnknownNullness"
- message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://android.github.io/kotlin-guides/interop.html#nullability-annotations"
- errorLine1=" public LongSparseArray<E> clone() {"
- errorLine2=" ~~~~~~~~~~~~~~~~~~">
- <location
- file="src/main/java/androidx/collection/LongSparseArray.java"
- line="85"
- column="12"/>
- </issue>
-
- <issue
- id="UnknownNullness"
- message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://android.github.io/kotlin-guides/interop.html#nullability-annotations"
+ message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://developer.android.com/kotlin/interop#nullability_annotations"
errorLine1=" public boolean remove(long key, Object value) {"
errorLine2=" ~~~~~~">
<location
- file="src/main/java/androidx/collection/LongSparseArray.java"
+ file="src/jvmMain/java/androidx/collection/LongSparseArray.java"
line="155"
column="37"/>
</issue>
<issue
id="UnknownNullness"
- message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://android.github.io/kotlin-guides/interop.html#nullability-annotations"
+ message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://developer.android.com/kotlin/interop#nullability_annotations"
errorLine1=" public synchronized final Map<K, V> snapshot() {"
errorLine2=" ~~~~~~~~~">
<location
- file="src/main/java/androidx/collection/LruCache.java"
+ file="src/jvmMain/java/androidx/collection/LruCache.java"
line="343"
column="31"/>
</issue>
<issue
id="UnknownNullness"
- message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://android.github.io/kotlin-guides/interop.html#nullability-annotations"
+ message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://developer.android.com/kotlin/interop#nullability_annotations"
errorLine1=" public SimpleArrayMap(SimpleArrayMap<K, V> map) {"
errorLine2=" ~~~~~~~~~~~~~~~~~~~~">
<location
- file="src/main/java/androidx/collection/SimpleArrayMap.java"
+ file="src/jvmMain/java/androidx/collection/SimpleArrayMap.java"
line="259"
column="27"/>
</issue>
<issue
id="UnknownNullness"
- message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://android.github.io/kotlin-guides/interop.html#nullability-annotations"
+ message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://developer.android.com/kotlin/interop#nullability_annotations"
errorLine1=" public boolean containsValue(Object value) {"
errorLine2=" ~~~~~~">
<location
- file="src/main/java/androidx/collection/SimpleArrayMap.java"
+ file="src/jvmMain/java/androidx/collection/SimpleArrayMap.java"
line="351"
column="34"/>
</issue>
<issue
id="UnknownNullness"
- message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://android.github.io/kotlin-guides/interop.html#nullability-annotations"
+ message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://developer.android.com/kotlin/interop#nullability_annotations"
errorLine1=" public V get(Object key) {"
errorLine2=" ~~~~~~">
<location
- file="src/main/java/androidx/collection/SimpleArrayMap.java"
+ file="src/jvmMain/java/androidx/collection/SimpleArrayMap.java"
line="363"
column="18"/>
</issue>
<issue
id="UnknownNullness"
- message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://android.github.io/kotlin-guides/interop.html#nullability-annotations"
+ message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://developer.android.com/kotlin/interop#nullability_annotations"
errorLine1=" public V getOrDefault(Object key, V defaultValue) {"
errorLine2=" ~~~~~~">
<location
- file="src/main/java/androidx/collection/SimpleArrayMap.java"
+ file="src/jvmMain/java/androidx/collection/SimpleArrayMap.java"
line="380"
column="27"/>
</issue>
<issue
id="UnknownNullness"
- message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://android.github.io/kotlin-guides/interop.html#nullability-annotations"
+ message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://developer.android.com/kotlin/interop#nullability_annotations"
errorLine1=" public V remove(Object key) {"
errorLine2=" ~~~~~~">
<location
- file="src/main/java/androidx/collection/SimpleArrayMap.java"
+ file="src/jvmMain/java/androidx/collection/SimpleArrayMap.java"
line="542"
column="21"/>
</issue>
<issue
id="UnknownNullness"
- message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://android.github.io/kotlin-guides/interop.html#nullability-annotations"
+ message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://developer.android.com/kotlin/interop#nullability_annotations"
errorLine1=" public boolean remove(Object key, Object value) {"
errorLine2=" ~~~~~~">
<location
- file="src/main/java/androidx/collection/SimpleArrayMap.java"
+ file="src/jvmMain/java/androidx/collection/SimpleArrayMap.java"
line="557"
column="27"/>
</issue>
<issue
id="UnknownNullness"
- message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://android.github.io/kotlin-guides/interop.html#nullability-annotations"
+ message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://developer.android.com/kotlin/interop#nullability_annotations"
errorLine1=" public boolean remove(Object key, Object value) {"
errorLine2=" ~~~~~~">
<location
- file="src/main/java/androidx/collection/SimpleArrayMap.java"
+ file="src/jvmMain/java/androidx/collection/SimpleArrayMap.java"
line="557"
column="39"/>
</issue>
<issue
id="UnknownNullness"
- message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://android.github.io/kotlin-guides/interop.html#nullability-annotations"
- errorLine1=" public SparseArrayCompat<E> clone() {"
- errorLine2=" ~~~~~~~~~~~~~~~~~~~~">
- <location
- file="src/main/java/androidx/collection/SparseArrayCompat.java"
- line="85"
- column="12"/>
- </issue>
-
- <issue
- id="UnknownNullness"
- message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://android.github.io/kotlin-guides/interop.html#nullability-annotations"
+ message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://developer.android.com/kotlin/interop#nullability_annotations"
errorLine1=" public boolean remove(int key, Object value) {"
errorLine2=" ~~~~~~">
<location
- file="src/main/java/androidx/collection/SparseArrayCompat.java"
+ file="src/jvmMain/java/androidx/collection/SparseArrayCompat.java"
line="155"
column="36"/>
</issue>
diff --git a/collection/collection/src/main/baseline-prof.txt b/collection/collection/src/jvmMain/baseline-prof.txt
similarity index 100%
rename from collection/collection/src/main/baseline-prof.txt
rename to collection/collection/src/jvmMain/baseline-prof.txt
diff --git a/collection/collection/src/main/java/androidx/collection/ArrayMap.java b/collection/collection/src/jvmMain/java/androidx/collection/ArrayMap.java
similarity index 100%
rename from collection/collection/src/main/java/androidx/collection/ArrayMap.java
rename to collection/collection/src/jvmMain/java/androidx/collection/ArrayMap.java
diff --git a/collection/collection/src/main/java/androidx/collection/ArraySet.java b/collection/collection/src/jvmMain/java/androidx/collection/ArraySet.java
similarity index 100%
rename from collection/collection/src/main/java/androidx/collection/ArraySet.java
rename to collection/collection/src/jvmMain/java/androidx/collection/ArraySet.java
diff --git a/collection/collection/src/main/java/androidx/collection/CircularArray.java b/collection/collection/src/jvmMain/java/androidx/collection/CircularArray.java
similarity index 100%
rename from collection/collection/src/main/java/androidx/collection/CircularArray.java
rename to collection/collection/src/jvmMain/java/androidx/collection/CircularArray.java
diff --git a/collection/collection/src/main/java/androidx/collection/CircularIntArray.java b/collection/collection/src/jvmMain/java/androidx/collection/CircularIntArray.java
similarity index 100%
rename from collection/collection/src/main/java/androidx/collection/CircularIntArray.java
rename to collection/collection/src/jvmMain/java/androidx/collection/CircularIntArray.java
diff --git a/collection/collection/src/main/java/androidx/collection/ContainerHelpers.java b/collection/collection/src/jvmMain/java/androidx/collection/ContainerHelpers.java
similarity index 100%
rename from collection/collection/src/main/java/androidx/collection/ContainerHelpers.java
rename to collection/collection/src/jvmMain/java/androidx/collection/ContainerHelpers.java
diff --git a/collection/collection/src/main/java/androidx/collection/IndexBasedArrayIterator.java b/collection/collection/src/jvmMain/java/androidx/collection/IndexBasedArrayIterator.java
similarity index 100%
rename from collection/collection/src/main/java/androidx/collection/IndexBasedArrayIterator.java
rename to collection/collection/src/jvmMain/java/androidx/collection/IndexBasedArrayIterator.java
diff --git a/collection/collection/src/main/java/androidx/collection/LongSparseArray.java b/collection/collection/src/jvmMain/java/androidx/collection/LongSparseArray.java
similarity index 100%
rename from collection/collection/src/main/java/androidx/collection/LongSparseArray.java
rename to collection/collection/src/jvmMain/java/androidx/collection/LongSparseArray.java
diff --git a/collection/collection/src/main/java/androidx/collection/LruCache.java b/collection/collection/src/jvmMain/java/androidx/collection/LruCache.java
similarity index 100%
rename from collection/collection/src/main/java/androidx/collection/LruCache.java
rename to collection/collection/src/jvmMain/java/androidx/collection/LruCache.java
diff --git a/collection/collection/src/main/java/androidx/collection/SimpleArrayMap.java b/collection/collection/src/jvmMain/java/androidx/collection/SimpleArrayMap.java
similarity index 100%
rename from collection/collection/src/main/java/androidx/collection/SimpleArrayMap.java
rename to collection/collection/src/jvmMain/java/androidx/collection/SimpleArrayMap.java
diff --git a/collection/collection/src/main/java/androidx/collection/SparseArrayCompat.java b/collection/collection/src/jvmMain/java/androidx/collection/SparseArrayCompat.java
similarity index 100%
rename from collection/collection/src/main/java/androidx/collection/SparseArrayCompat.java
rename to collection/collection/src/jvmMain/java/androidx/collection/SparseArrayCompat.java
diff --git a/collection/collection/src/main/java/androidx/collection/package-info.java b/collection/collection/src/jvmMain/java/androidx/collection/package-info.java
similarity index 100%
rename from collection/collection/src/main/java/androidx/collection/package-info.java
rename to collection/collection/src/jvmMain/java/androidx/collection/package-info.java
diff --git a/collection/collection/src/test/java/androidx/collection/ArrayMapCompatTest.java b/collection/collection/src/jvmTest/java/androidx/collection/ArrayMapCompatTest.java
similarity index 100%
rename from collection/collection/src/test/java/androidx/collection/ArrayMapCompatTest.java
rename to collection/collection/src/jvmTest/java/androidx/collection/ArrayMapCompatTest.java
diff --git a/collection/collection/src/test/java/androidx/collection/ArrayMapTest.java b/collection/collection/src/jvmTest/java/androidx/collection/ArrayMapTest.java
similarity index 100%
rename from collection/collection/src/test/java/androidx/collection/ArrayMapTest.java
rename to collection/collection/src/jvmTest/java/androidx/collection/ArrayMapTest.java
diff --git a/collection/collection/src/test/java/androidx/collection/ArraySetCompatTest.java b/collection/collection/src/jvmTest/java/androidx/collection/ArraySetCompatTest.java
similarity index 100%
rename from collection/collection/src/test/java/androidx/collection/ArraySetCompatTest.java
rename to collection/collection/src/jvmTest/java/androidx/collection/ArraySetCompatTest.java
diff --git a/collection/collection/src/test/java/androidx/collection/ArraySetTest.java b/collection/collection/src/jvmTest/java/androidx/collection/ArraySetTest.java
similarity index 100%
rename from collection/collection/src/test/java/androidx/collection/ArraySetTest.java
rename to collection/collection/src/jvmTest/java/androidx/collection/ArraySetTest.java
diff --git a/collection/collection/src/test/java/androidx/collection/CircularArrayTest.java b/collection/collection/src/jvmTest/java/androidx/collection/CircularArrayTest.java
similarity index 100%
rename from collection/collection/src/test/java/androidx/collection/CircularArrayTest.java
rename to collection/collection/src/jvmTest/java/androidx/collection/CircularArrayTest.java
diff --git a/collection/collection/src/test/java/androidx/collection/CircularIntArrayTest.java b/collection/collection/src/jvmTest/java/androidx/collection/CircularIntArrayTest.java
similarity index 100%
rename from collection/collection/src/test/java/androidx/collection/CircularIntArrayTest.java
rename to collection/collection/src/jvmTest/java/androidx/collection/CircularIntArrayTest.java
diff --git a/collection/collection/src/test/java/androidx/collection/IndexBasedArrayIteratorTest.java b/collection/collection/src/jvmTest/java/androidx/collection/IndexBasedArrayIteratorTest.java
similarity index 100%
rename from collection/collection/src/test/java/androidx/collection/IndexBasedArrayIteratorTest.java
rename to collection/collection/src/jvmTest/java/androidx/collection/IndexBasedArrayIteratorTest.java
diff --git a/collection/collection/src/test/java/androidx/collection/LongSparseArrayTest.java b/collection/collection/src/jvmTest/java/androidx/collection/LongSparseArrayTest.java
similarity index 100%
rename from collection/collection/src/test/java/androidx/collection/LongSparseArrayTest.java
rename to collection/collection/src/jvmTest/java/androidx/collection/LongSparseArrayTest.java
diff --git a/collection/collection/src/test/java/androidx/collection/LruCacheTest.java b/collection/collection/src/jvmTest/java/androidx/collection/LruCacheTest.java
similarity index 100%
rename from collection/collection/src/test/java/androidx/collection/LruCacheTest.java
rename to collection/collection/src/jvmTest/java/androidx/collection/LruCacheTest.java
diff --git a/collection/collection/src/test/java/androidx/collection/SimpleArrayMapTest.java b/collection/collection/src/jvmTest/java/androidx/collection/SimpleArrayMapTest.java
similarity index 100%
rename from collection/collection/src/test/java/androidx/collection/SimpleArrayMapTest.java
rename to collection/collection/src/jvmTest/java/androidx/collection/SimpleArrayMapTest.java
diff --git a/collection/collection/src/test/java/androidx/collection/SparseArrayCompatTest.java b/collection/collection/src/jvmTest/java/androidx/collection/SparseArrayCompatTest.java
similarity index 100%
rename from collection/collection/src/test/java/androidx/collection/SparseArrayCompatTest.java
rename to collection/collection/src/jvmTest/java/androidx/collection/SparseArrayCompatTest.java
diff --git a/compose/foundation/foundation-layout/api/current.txt b/compose/foundation/foundation-layout/api/current.txt
index 9f1eed7..691783f 100644
--- a/compose/foundation/foundation-layout/api/current.txt
+++ b/compose/foundation/foundation-layout/api/current.txt
@@ -223,6 +223,7 @@
method public static androidx.compose.foundation.layout.WindowInsets add(androidx.compose.foundation.layout.WindowInsets, androidx.compose.foundation.layout.WindowInsets insets);
method @androidx.compose.runtime.Composable public static androidx.compose.foundation.layout.PaddingValues asPaddingValues(androidx.compose.foundation.layout.WindowInsets);
method public static androidx.compose.foundation.layout.WindowInsets exclude(androidx.compose.foundation.layout.WindowInsets, androidx.compose.foundation.layout.WindowInsets insets);
+ method public static androidx.compose.foundation.layout.WindowInsets only(androidx.compose.foundation.layout.WindowInsets, int sides);
method public static androidx.compose.foundation.layout.WindowInsets union(androidx.compose.foundation.layout.WindowInsets, androidx.compose.foundation.layout.WindowInsets insets);
}
@@ -245,6 +246,30 @@
method public static androidx.compose.ui.Modifier waterfallPadding(androidx.compose.ui.Modifier);
}
+ @kotlin.jvm.JvmInline public final value class WindowInsetsSides {
+ method public operator int plus(int sides);
+ field public static final androidx.compose.foundation.layout.WindowInsetsSides.Companion Companion;
+ }
+
+ public static final class WindowInsetsSides.Companion {
+ method public int getBottom();
+ method public int getEnd();
+ method public int getHorizontal();
+ method public int getLeft();
+ method public int getRight();
+ method public int getStart();
+ method public int getTop();
+ method public int getVertical();
+ property public final int Bottom;
+ property public final int End;
+ property public final int Horizontal;
+ property public final int Left;
+ property public final int Right;
+ property public final int Start;
+ property public final int Top;
+ property public final int Vertical;
+ }
+
public final class WindowInsetsSizeKt {
method @androidx.compose.runtime.Stable public static androidx.compose.ui.Modifier windowInsetsBottomHeight(androidx.compose.ui.Modifier, androidx.compose.foundation.layout.WindowInsets insets);
method @androidx.compose.runtime.Stable public static androidx.compose.ui.Modifier windowInsetsEndWidth(androidx.compose.ui.Modifier, androidx.compose.foundation.layout.WindowInsets insets);
diff --git a/compose/foundation/foundation-layout/api/public_plus_experimental_current.txt b/compose/foundation/foundation-layout/api/public_plus_experimental_current.txt
index ea81a4f..f2c39f2 100644
--- a/compose/foundation/foundation-layout/api/public_plus_experimental_current.txt
+++ b/compose/foundation/foundation-layout/api/public_plus_experimental_current.txt
@@ -226,6 +226,7 @@
method public static androidx.compose.foundation.layout.WindowInsets add(androidx.compose.foundation.layout.WindowInsets, androidx.compose.foundation.layout.WindowInsets insets);
method @androidx.compose.runtime.Composable public static androidx.compose.foundation.layout.PaddingValues asPaddingValues(androidx.compose.foundation.layout.WindowInsets);
method public static androidx.compose.foundation.layout.WindowInsets exclude(androidx.compose.foundation.layout.WindowInsets, androidx.compose.foundation.layout.WindowInsets insets);
+ method public static androidx.compose.foundation.layout.WindowInsets only(androidx.compose.foundation.layout.WindowInsets, int sides);
method public static androidx.compose.foundation.layout.WindowInsets union(androidx.compose.foundation.layout.WindowInsets, androidx.compose.foundation.layout.WindowInsets insets);
}
@@ -250,6 +251,30 @@
method public static androidx.compose.ui.Modifier waterfallPadding(androidx.compose.ui.Modifier);
}
+ @kotlin.jvm.JvmInline public final value class WindowInsetsSides {
+ method public operator int plus(int sides);
+ field public static final androidx.compose.foundation.layout.WindowInsetsSides.Companion Companion;
+ }
+
+ public static final class WindowInsetsSides.Companion {
+ method public int getBottom();
+ method public int getEnd();
+ method public int getHorizontal();
+ method public int getLeft();
+ method public int getRight();
+ method public int getStart();
+ method public int getTop();
+ method public int getVertical();
+ property public final int Bottom;
+ property public final int End;
+ property public final int Horizontal;
+ property public final int Left;
+ property public final int Right;
+ property public final int Start;
+ property public final int Top;
+ property public final int Vertical;
+ }
+
public final class WindowInsetsSizeKt {
method @androidx.compose.runtime.Stable public static androidx.compose.ui.Modifier windowInsetsBottomHeight(androidx.compose.ui.Modifier, androidx.compose.foundation.layout.WindowInsets insets);
method @androidx.compose.runtime.Stable public static androidx.compose.ui.Modifier windowInsetsEndWidth(androidx.compose.ui.Modifier, androidx.compose.foundation.layout.WindowInsets insets);
diff --git a/compose/foundation/foundation-layout/api/restricted_current.txt b/compose/foundation/foundation-layout/api/restricted_current.txt
index 2fccf29..c31ede2 100644
--- a/compose/foundation/foundation-layout/api/restricted_current.txt
+++ b/compose/foundation/foundation-layout/api/restricted_current.txt
@@ -228,6 +228,7 @@
method public static androidx.compose.foundation.layout.WindowInsets add(androidx.compose.foundation.layout.WindowInsets, androidx.compose.foundation.layout.WindowInsets insets);
method @androidx.compose.runtime.Composable public static androidx.compose.foundation.layout.PaddingValues asPaddingValues(androidx.compose.foundation.layout.WindowInsets);
method public static androidx.compose.foundation.layout.WindowInsets exclude(androidx.compose.foundation.layout.WindowInsets, androidx.compose.foundation.layout.WindowInsets insets);
+ method public static androidx.compose.foundation.layout.WindowInsets only(androidx.compose.foundation.layout.WindowInsets, int sides);
method public static androidx.compose.foundation.layout.WindowInsets union(androidx.compose.foundation.layout.WindowInsets, androidx.compose.foundation.layout.WindowInsets insets);
}
@@ -250,6 +251,30 @@
method public static androidx.compose.ui.Modifier waterfallPadding(androidx.compose.ui.Modifier);
}
+ @kotlin.jvm.JvmInline public final value class WindowInsetsSides {
+ method public operator int plus(int sides);
+ field public static final androidx.compose.foundation.layout.WindowInsetsSides.Companion Companion;
+ }
+
+ public static final class WindowInsetsSides.Companion {
+ method public int getBottom();
+ method public int getEnd();
+ method public int getHorizontal();
+ method public int getLeft();
+ method public int getRight();
+ method public int getStart();
+ method public int getTop();
+ method public int getVertical();
+ property public final int Bottom;
+ property public final int End;
+ property public final int Horizontal;
+ property public final int Left;
+ property public final int Right;
+ property public final int Start;
+ property public final int Top;
+ property public final int Vertical;
+ }
+
public final class WindowInsetsSizeKt {
method @androidx.compose.runtime.Stable public static androidx.compose.ui.Modifier windowInsetsBottomHeight(androidx.compose.ui.Modifier, androidx.compose.foundation.layout.WindowInsets insets);
method @androidx.compose.runtime.Stable public static androidx.compose.ui.Modifier windowInsetsEndWidth(androidx.compose.ui.Modifier, androidx.compose.foundation.layout.WindowInsets insets);
diff --git a/compose/foundation/foundation-layout/build.gradle b/compose/foundation/foundation-layout/build.gradle
index be6e5ed..c92c558 100644
--- a/compose/foundation/foundation-layout/build.gradle
+++ b/compose/foundation/foundation-layout/build.gradle
@@ -46,6 +46,7 @@
testImplementation(libs.testRules)
testImplementation(libs.testRunner)
testImplementation(libs.junit)
+ testImplementation(libs.truth)
androidTestImplementation(project(":compose:foundation:foundation"))
androidTestImplementation("androidx.compose.ui:ui-test-junit4:1.0.0")
@@ -103,6 +104,7 @@
implementation(libs.testRules)
implementation(libs.testRunner)
implementation(libs.junit)
+ implementation(libs.truth)
}
androidAndroidTest.dependencies {
diff --git a/compose/foundation/foundation-layout/src/commonMain/kotlin/androidx/compose/foundation/layout/WindowInsets.kt b/compose/foundation/foundation-layout/src/commonMain/kotlin/androidx/compose/foundation/layout/WindowInsets.kt
index 627e0f5..a1ce2ec 100644
--- a/compose/foundation/foundation-layout/src/commonMain/kotlin/androidx/compose/foundation/layout/WindowInsets.kt
+++ b/compose/foundation/foundation-layout/src/commonMain/kotlin/androidx/compose/foundation/layout/WindowInsets.kt
@@ -66,6 +66,97 @@
}
/**
+ * [WindowInsetsSides] is used in [WindowInsets.only] to define which sides of the
+ * [WindowInsets] should apply.
+ */
+@kotlin.jvm.JvmInline
+value class WindowInsetsSides private constructor(private val value: Int) {
+ /**
+ * Returns a [WindowInsetsSides] containing sides defied in [sides] and the
+ * sides in `this`.
+ */
+ operator fun plus(sides: WindowInsetsSides): WindowInsetsSides =
+ WindowInsetsSides(value or sides.value)
+
+ internal fun hasAny(sides: WindowInsetsSides): Boolean =
+ (value and sides.value) != 0
+
+ companion object {
+ // _---- allowLeft in ltr
+ // /
+ // | _--- allowRight in ltr
+ // |/
+ // || _-- allowLeft in rtl
+ // ||/
+ // ||| _- allowRight in rtl
+ // |||/
+ // VVVV
+ // Mask = ----
+ //
+ // Left = 1010
+ // Right = 0101
+ // Start = 1001
+ // End = 0110
+
+ internal val AllowLeftInLtr = WindowInsetsSides(1 shl 3)
+ internal val AllowRightInLtr = WindowInsetsSides(1 shl 2)
+ internal val AllowLeftInRtl = WindowInsetsSides(1 shl 1)
+ internal val AllowRightInRtl = WindowInsetsSides(1 shl 0)
+
+ /**
+ * Indicates a [WindowInsets] start side, which is left or right
+ * depending on [LayoutDirection]. If [LayoutDirection.Ltr], [Start]
+ * is the left side. If [LayoutDirection.Rtl], [Start] is the right side.
+ *
+ * Use [Left] or [Right] if the physical direction is required.
+ */
+ val Start = AllowLeftInLtr + AllowRightInRtl
+
+ /**
+ * Indicates a [WindowInsets] end side, which is left or right
+ * depending on [LayoutDirection]. If [LayoutDirection.Ltr], [End]
+ * is the right side. If [LayoutDirection.Rtl], [End] is the left side.
+ *
+ * Use [Left] or [Right] if the physical direction is required.
+ */
+ val End = AllowRightInLtr + AllowLeftInRtl
+
+ /**
+ * Indicates a [WindowInsets] top side.
+ */
+ val Top = WindowInsetsSides(1 shl 4)
+
+ /**
+ * Indicates a [WindowInsets] bottom side.
+ */
+ val Bottom = WindowInsetsSides(1 shl 5)
+
+ /**
+ * Indicates a [WindowInsets] left side. Most layouts will prefer using
+ * [Start] or [End] to account for [LayoutDirection].
+ */
+ val Left = AllowLeftInLtr + AllowLeftInRtl
+
+ /**
+ * Indicates a [WindowInsets] right side. Most layouts will prefer using
+ * [Start] or [End] to account for [LayoutDirection].
+ */
+ val Right = AllowRightInLtr + AllowRightInRtl
+
+ /**
+ * Indicates a [WindowInsets] horizontal sides. This is a combination of
+ * [Left] and [Right] sides, or [Start] and [End] sides.
+ */
+ val Horizontal = Left + Right
+
+ /**
+ * Indicates a [WindowInsets] [Top] and [Bottom] sides.
+ */
+ val Vertical = Top + Bottom
+ }
+}
+
+/**
* Returns an [WindowInsets] that has the maximum values of this [WindowInsets] and [insets].
*/
fun WindowInsets.union(insets: WindowInsets): WindowInsets = UnionInsets(this, insets)
@@ -83,13 +174,20 @@
fun WindowInsets.exclude(insets: WindowInsets): WindowInsets = ExcludeInsets(this, insets)
/**
- * Returns the an [WindowInsets] that has values of this, added to the values of [insets].
+ * Returns a [WindowInsets] that has values of this, added to the values of [insets].
* For example, if this has a top of 10 and insets has a top of 5, the returned [WindowInsets]
* will have a top of 15.
*/
fun WindowInsets.add(insets: WindowInsets): WindowInsets = AddedInsets(this, insets)
/**
+ * Returns a [WindowInsets] that eliminates all dimensions except the ones that are enabled.
+ * For example, to have a [WindowInsets] at the bottom of the screen, pass
+ * [WindowInsetsSides.Bottom].
+ */
+fun WindowInsets.only(sides: WindowInsetsSides): WindowInsets = LimitInsets(this, sides)
+
+/**
* Convert an [WindowInsets] to a [PaddingValues] and uses [LocalDensity] for DP to pixel conversion.
* [PaddingValues] can be passed to some containers to pad internal content so that it doesn't
* overlap the insets when fully scrolled. Ensure that the insets are [consumed][consumedWindowInsets]
@@ -415,6 +513,63 @@
}
@Stable
+private class LimitInsets(
+ val insets: WindowInsets,
+ val sides: WindowInsetsSides
+) : WindowInsets {
+ override fun getLeft(density: Density, layoutDirection: LayoutDirection): Int {
+ val layoutDirectionSide = if (layoutDirection == LayoutDirection.Ltr) {
+ WindowInsetsSides.AllowLeftInLtr
+ } else {
+ WindowInsetsSides.AllowLeftInRtl
+ }
+ val allowLeft = sides.hasAny(layoutDirectionSide)
+ return if (allowLeft) {
+ insets.getLeft(density, layoutDirection)
+ } else {
+ 0
+ }
+ }
+
+ override fun getTop(density: Density): Int =
+ if (sides.hasAny(WindowInsetsSides.Top)) insets.getTop(density) else 0
+
+ override fun getRight(density: Density, layoutDirection: LayoutDirection): Int {
+ val layoutDirectionSide = if (layoutDirection == LayoutDirection.Ltr) {
+ WindowInsetsSides.AllowRightInLtr
+ } else {
+ WindowInsetsSides.AllowRightInRtl
+ }
+ val allowRight = sides.hasAny(layoutDirectionSide)
+ return if (allowRight) {
+ insets.getRight(density, layoutDirection)
+ } else {
+ 0
+ }
+ }
+
+ override fun getBottom(density: Density): Int =
+ if (sides.hasAny(WindowInsetsSides.Bottom)) insets.getBottom(density) else 0
+
+ override fun equals(other: Any?): Boolean {
+ if (this === other) {
+ return true
+ }
+ if (other !is LimitInsets) {
+ return false
+ }
+ return insets == other.insets &&
+ sides == other.sides
+ }
+
+ override fun hashCode(): Int {
+ var result = insets.hashCode()
+ result = 31 * result + sides.hashCode()
+ return result
+ }
+}
+
+@Stable
private class InsetsPaddingValues(
val insets: WindowInsets,
private val density: Density
diff --git a/compose/foundation/foundation-layout/src/test/kotlin/androidx/compose/foundation/layout/WindowInsetsTest.kt b/compose/foundation/foundation-layout/src/test/kotlin/androidx/compose/foundation/layout/WindowInsetsTest.kt
new file mode 100644
index 0000000..a06bf1d
--- /dev/null
+++ b/compose/foundation/foundation-layout/src/test/kotlin/androidx/compose/foundation/layout/WindowInsetsTest.kt
@@ -0,0 +1,204 @@
+/*
+ * 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.foundation.text
+
+import androidx.compose.foundation.layout.InsetsValues
+import androidx.compose.foundation.layout.ValueInsets
+import androidx.compose.foundation.layout.WindowInsets
+import androidx.compose.foundation.layout.WindowInsetsSides
+import androidx.compose.foundation.layout.add
+import androidx.compose.foundation.layout.exclude
+import androidx.compose.foundation.layout.only
+import androidx.compose.foundation.layout.union
+import androidx.compose.ui.unit.Density
+import androidx.compose.ui.unit.LayoutDirection
+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 WindowInsetsTest {
+ private val density = Density(density = 1f)
+ private val doubleDensity = Density(density = 2f)
+
+ @Test
+ fun valueInsets() {
+ val insetsValues = InsetsValues(10, 11, 12, 13)
+ val insets = ValueInsets(insetsValues, "hello")
+
+ assertThat(insets.getLeft(density, LayoutDirection.Ltr)).isEqualTo(10)
+ assertThat(insets.getTop(density)).isEqualTo(11)
+ assertThat(insets.getRight(density, LayoutDirection.Ltr)).isEqualTo(12)
+ assertThat(insets.getBottom(density)).isEqualTo(13)
+
+ assertThat(insets.getLeft(doubleDensity, LayoutDirection.Ltr)).isEqualTo(10)
+ assertThat(insets.getLeft(density, LayoutDirection.Rtl)).isEqualTo(10)
+ }
+
+ @Test
+ fun fixedIntInsets() {
+ val insets = WindowInsets(10, 11, 12, 13)
+
+ assertThat(insets.getLeft(density, LayoutDirection.Ltr)).isEqualTo(10)
+ assertThat(insets.getTop(density)).isEqualTo(11)
+ assertThat(insets.getRight(density, LayoutDirection.Ltr)).isEqualTo(12)
+ assertThat(insets.getBottom(density)).isEqualTo(13)
+
+ assertThat(insets.getLeft(doubleDensity, LayoutDirection.Ltr)).isEqualTo(10)
+ assertThat(insets.getLeft(density, LayoutDirection.Rtl)).isEqualTo(10)
+ }
+
+ @Test
+ fun fixedDpInsets() {
+ val insets = WindowInsets(10.dp, 11.dp, 12.dp, 13.dp)
+
+ assertThat(insets.getLeft(density, LayoutDirection.Ltr)).isEqualTo(10)
+ assertThat(insets.getTop(density)).isEqualTo(11)
+ assertThat(insets.getRight(density, LayoutDirection.Ltr)).isEqualTo(12)
+ assertThat(insets.getBottom(density)).isEqualTo(13)
+
+ assertThat(insets.getLeft(doubleDensity, LayoutDirection.Ltr)).isEqualTo(20)
+ assertThat(insets.getLeft(density, LayoutDirection.Rtl)).isEqualTo(10)
+ }
+
+ @Test
+ fun union() {
+ val first = WindowInsets(10, 11, 12, 13)
+ val second = WindowInsets(5, 20, 14, 2)
+ val union = first.union(second)
+ assertThat(union.getLeft(density, LayoutDirection.Ltr)).isEqualTo(10)
+ assertThat(union.getTop(density)).isEqualTo(20)
+ assertThat(union.getRight(density, LayoutDirection.Ltr)).isEqualTo(14)
+ assertThat(union.getBottom(density)).isEqualTo(13)
+ }
+
+ @Test
+ fun exclude() {
+ val first = WindowInsets(10, 11, 12, 13)
+ val second = WindowInsets(5, 20, 14, 2)
+ val exclude = first.exclude(second)
+ assertThat(exclude.getLeft(density, LayoutDirection.Ltr)).isEqualTo(5)
+ assertThat(exclude.getTop(density)).isEqualTo(0)
+ assertThat(exclude.getRight(density, LayoutDirection.Ltr)).isEqualTo(0)
+ assertThat(exclude.getBottom(density)).isEqualTo(11)
+ }
+
+ @Test
+ fun add() {
+ val first = WindowInsets(10, 11, 12, 13)
+ val second = WindowInsets(5, 20, 14, 2)
+ val add = first.add(second)
+ assertThat(add.getLeft(density, LayoutDirection.Ltr)).isEqualTo(15)
+ assertThat(add.getTop(density)).isEqualTo(31)
+ assertThat(add.getRight(density, LayoutDirection.Ltr)).isEqualTo(26)
+ assertThat(add.getBottom(density)).isEqualTo(15)
+ }
+
+ @Test
+ fun onlyStart() {
+ val insets = WindowInsets(10, 11, 12, 13).only(WindowInsetsSides.Start)
+ assertThat(insets.getLeft(density, LayoutDirection.Ltr)).isEqualTo(10)
+ assertThat(insets.getTop(density)).isEqualTo(0)
+ assertThat(insets.getRight(density, LayoutDirection.Ltr)).isEqualTo(0)
+ assertThat(insets.getBottom(density)).isEqualTo(0)
+ assertThat(insets.getLeft(density, LayoutDirection.Rtl)).isEqualTo(0)
+ assertThat(insets.getRight(density, LayoutDirection.Rtl)).isEqualTo(12)
+ }
+
+ @Test
+ fun onlyTop() {
+ val insets = WindowInsets(10, 11, 12, 13).only(WindowInsetsSides.Top)
+ assertThat(insets.getLeft(density, LayoutDirection.Ltr)).isEqualTo(0)
+ assertThat(insets.getTop(density)).isEqualTo(11)
+ assertThat(insets.getRight(density, LayoutDirection.Ltr)).isEqualTo(0)
+ assertThat(insets.getBottom(density)).isEqualTo(0)
+ }
+
+ @Test
+ fun onlyEnd() {
+ val insets = WindowInsets(10, 11, 12, 13).only(WindowInsetsSides.End)
+ assertThat(insets.getLeft(density, LayoutDirection.Ltr)).isEqualTo(0)
+ assertThat(insets.getTop(density)).isEqualTo(0)
+ assertThat(insets.getRight(density, LayoutDirection.Ltr)).isEqualTo(12)
+ assertThat(insets.getBottom(density)).isEqualTo(0)
+ assertThat(insets.getLeft(density, LayoutDirection.Rtl)).isEqualTo(10)
+ assertThat(insets.getRight(density, LayoutDirection.Rtl)).isEqualTo(0)
+ }
+
+ @Test
+ fun onlyBottom() {
+ val insets = WindowInsets(10, 11, 12, 13).only(WindowInsetsSides.Bottom)
+ assertThat(insets.getLeft(density, LayoutDirection.Ltr)).isEqualTo(0)
+ assertThat(insets.getTop(density)).isEqualTo(0)
+ assertThat(insets.getRight(density, LayoutDirection.Ltr)).isEqualTo(0)
+ assertThat(insets.getBottom(density)).isEqualTo(13)
+ }
+
+ @Test
+ fun onlyLeft() {
+ val insets = WindowInsets(10, 11, 12, 13).only(WindowInsetsSides.Left)
+ assertThat(insets.getLeft(density, LayoutDirection.Ltr)).isEqualTo(10)
+ assertThat(insets.getTop(density)).isEqualTo(0)
+ assertThat(insets.getRight(density, LayoutDirection.Ltr)).isEqualTo(0)
+ assertThat(insets.getBottom(density)).isEqualTo(0)
+ assertThat(insets.getLeft(density, LayoutDirection.Rtl)).isEqualTo(10)
+ assertThat(insets.getRight(density, LayoutDirection.Rtl)).isEqualTo(0)
+ }
+
+ @Test
+ fun onlyRight() {
+ val insets = WindowInsets(10, 11, 12, 13).only(WindowInsetsSides.Right)
+ assertThat(insets.getLeft(density, LayoutDirection.Ltr)).isEqualTo(0)
+ assertThat(insets.getTop(density)).isEqualTo(0)
+ assertThat(insets.getRight(density, LayoutDirection.Ltr)).isEqualTo(12)
+ assertThat(insets.getBottom(density)).isEqualTo(0)
+ assertThat(insets.getLeft(density, LayoutDirection.Rtl)).isEqualTo(0)
+ assertThat(insets.getRight(density, LayoutDirection.Rtl)).isEqualTo(12)
+ }
+
+ @Test
+ fun onlyHorizontal() {
+ val insets = WindowInsets(10, 11, 12, 13).only(WindowInsetsSides.Horizontal)
+ assertThat(insets.getLeft(density, LayoutDirection.Ltr)).isEqualTo(10)
+ assertThat(insets.getTop(density)).isEqualTo(0)
+ assertThat(insets.getRight(density, LayoutDirection.Ltr)).isEqualTo(12)
+ assertThat(insets.getBottom(density)).isEqualTo(0)
+ assertThat(insets.getLeft(density, LayoutDirection.Rtl)).isEqualTo(10)
+ assertThat(insets.getRight(density, LayoutDirection.Rtl)).isEqualTo(12)
+ }
+
+ @Test
+ fun onlyVertical() {
+ val insets = WindowInsets(10, 11, 12, 13).only(WindowInsetsSides.Vertical)
+ assertThat(insets.getLeft(density, LayoutDirection.Ltr)).isEqualTo(0)
+ assertThat(insets.getTop(density)).isEqualTo(11)
+ assertThat(insets.getRight(density, LayoutDirection.Ltr)).isEqualTo(0)
+ assertThat(insets.getBottom(density)).isEqualTo(13)
+ }
+
+ @Test
+ fun plus() {
+ val insets = WindowInsets(10, 11, 12, 13)
+ .only(WindowInsetsSides.Vertical + WindowInsetsSides.Horizontal)
+ assertThat(insets.getLeft(density, LayoutDirection.Ltr)).isEqualTo(10)
+ assertThat(insets.getTop(density)).isEqualTo(11)
+ assertThat(insets.getRight(density, LayoutDirection.Ltr)).isEqualTo(12)
+ assertThat(insets.getBottom(density)).isEqualTo(13)
+ }
+}
diff --git a/compose/foundation/foundation/integration-tests/foundation-demos/src/main/java/androidx/compose/foundation/demos/text/ComposeText.kt b/compose/foundation/foundation/integration-tests/foundation-demos/src/main/java/androidx/compose/foundation/demos/text/ComposeText.kt
index c0e7425..217fdd6 100644
--- a/compose/foundation/foundation/integration-tests/foundation-demos/src/main/java/androidx/compose/foundation/demos/text/ComposeText.kt
+++ b/compose/foundation/foundation/integration-tests/foundation-demos/src/main/java/androidx/compose/foundation/demos/text/ComposeText.kt
@@ -16,17 +16,32 @@
package androidx.compose.foundation.demos.text
+import androidx.compose.animation.animateColor
+import androidx.compose.animation.core.RepeatMode
+import androidx.compose.animation.core.infiniteRepeatable
+import androidx.compose.animation.core.rememberInfiniteTransition
+import androidx.compose.animation.core.tween
+import androidx.compose.foundation.background
+import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxHeight
+import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.lazy.LazyColumn
+import androidx.compose.foundation.text.InlineTextContent
+import androidx.compose.foundation.text.appendInlineContent
import androidx.compose.material.Text
import androidx.compose.runtime.Composable
+import androidx.compose.runtime.CompositionLocalProvider
+import androidx.compose.runtime.getValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.Shadow
+import androidx.compose.ui.platform.LocalLayoutDirection
+import androidx.compose.ui.text.Placeholder
+import androidx.compose.ui.text.PlaceholderVerticalAlign
import androidx.compose.ui.text.SpanStyle
import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.text.buildAnnotatedString
@@ -51,6 +66,7 @@
import androidx.compose.ui.text.samples.TextStyleSample
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.text.withStyle
+import androidx.compose.ui.unit.LayoutDirection
import androidx.compose.ui.unit.em
import androidx.compose.ui.unit.sp
@@ -138,6 +154,10 @@
TagLine(tag = "textOverflow: Clip, Ellipsis, Visible")
TextDemoTextOverflow()
}
+ item {
+ TagLine(tag = "inline content")
+ TextDemoInlineContent()
+ }
}
}
@@ -539,4 +559,69 @@
TextOverflowVisibleFixedSizeSample()
SecondTagLine(tag = "overflow = TextOverflow.Visible with fixed width and min height")
TextOverflowVisibleMinHeightSample()
+}
+
+@Composable
+fun TextDemoInlineContent() {
+ val inlineContentId = "box"
+ val inlineTextContent = InlineTextContent(
+ placeholder = Placeholder(
+ width = 5.em,
+ height = 1.em,
+ placeholderVerticalAlign = PlaceholderVerticalAlign.AboveBaseline
+ )
+ ) {
+ val colorAnimation = rememberInfiniteTransition()
+ val color by colorAnimation.animateColor(
+ initialValue = Color.Red,
+ targetValue = Color.Blue,
+ animationSpec = infiniteRepeatable(tween(1000), RepeatMode.Reverse)
+ )
+ Box(modifier = Modifier.fillMaxSize().background(color))
+ }
+
+ Text(
+ text = buildAnnotatedString {
+ append("Here is a wide inline composable ")
+ appendInlineContent(inlineContentId)
+ append(" that is repeatedly changing its color.")
+ },
+ inlineContent = mapOf(inlineContentId to inlineTextContent),
+ modifier = Modifier.fillMaxWidth()
+ )
+
+ SecondTagLine(tag = "RTL Layout")
+ CompositionLocalProvider(LocalLayoutDirection provides LayoutDirection.Rtl) {
+ Text(
+ text = buildAnnotatedString {
+ append("Here is a wide inline composable ")
+ appendInlineContent(inlineContentId)
+ append(" that is repeatedly changing its color.")
+ },
+ inlineContent = mapOf(inlineContentId to inlineTextContent),
+ modifier = Modifier.fillMaxWidth()
+ )
+ }
+
+ SecondTagLine(tag = "Bidi Text - LTR/RTL")
+ Text(
+ text = buildAnnotatedString {
+ append("$displayText ")
+ appendInlineContent(inlineContentId)
+ append("$displayTextArabic ")
+ },
+ inlineContent = mapOf(inlineContentId to inlineTextContent),
+ modifier = Modifier.fillMaxWidth()
+ )
+
+ SecondTagLine(tag = "Bidi Text - RTL/LTR")
+ Text(
+ text = buildAnnotatedString {
+ append("$displayTextArabic ")
+ appendInlineContent(inlineContentId)
+ append("$displayText ")
+ },
+ inlineContent = mapOf(inlineContentId to inlineTextContent),
+ modifier = Modifier.fillMaxWidth()
+ )
}
\ No newline at end of file
diff --git a/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/lazy/grid/LazyGridTest.kt b/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/lazy/grid/LazyGridTest.kt
index f65a3ce..17e6f3b 100644
--- a/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/lazy/grid/LazyGridTest.kt
+++ b/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/lazy/grid/LazyGridTest.kt
@@ -16,6 +16,7 @@
package androidx.compose.foundation.lazy.grid
+import android.os.Build
import androidx.compose.foundation.AutoTestFrameClock
import androidx.compose.foundation.ExperimentalFoundationApi
import androidx.compose.foundation.gestures.scrollBy
@@ -42,8 +43,13 @@
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.setValue
+import androidx.compose.testutils.assertPixels
import androidx.compose.ui.Modifier
import androidx.compose.ui.composed
+import androidx.compose.ui.draw.drawBehind
+import androidx.compose.ui.geometry.Offset
+import androidx.compose.ui.geometry.Size
+import androidx.compose.ui.graphics.Color
import androidx.compose.ui.layout.layout
import androidx.compose.ui.platform.LocalLayoutDirection
import androidx.compose.ui.platform.testTag
@@ -58,12 +64,15 @@
import androidx.compose.ui.test.assertLeftPositionInRootIsEqualTo
import androidx.compose.ui.test.assertTopPositionInRootIsEqualTo
import androidx.compose.ui.test.assertWidthIsAtLeast
+import androidx.compose.ui.test.captureToImage
import androidx.compose.ui.test.junit4.createComposeRule
import androidx.compose.ui.test.onNodeWithTag
import androidx.compose.ui.unit.LayoutDirection
import androidx.compose.ui.unit.dp
+import androidx.compose.ui.zIndex
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.MediumTest
+import androidx.test.filters.SdkSuppress
import com.google.common.truth.Truth
import kotlinx.coroutines.runBlocking
import org.junit.Rule
@@ -857,6 +866,26 @@
}
@Test
+ fun passingNegativeItemsCountIsNotAllowed() {
+ var exception: Exception? = null
+ rule.setContentWithTestViewConfiguration {
+ LazyVerticalGrid(GridCells.Fixed(1)) {
+ try {
+ items(-1) {
+ Box(Modifier)
+ }
+ } catch (e: Exception) {
+ exception = e
+ }
+ }
+ }
+
+ rule.runOnIdle {
+ Truth.assertThat(exception).isInstanceOf(IllegalArgumentException::class.java)
+ }
+ }
+
+ @Test
fun recomposingWithNewComposedModifierObjectIsNotCausingRemeasure() {
var remeasureCount = 0
val layoutModifier = Modifier.layout { measurable, constraints ->
@@ -925,4 +954,34 @@
Truth.assertThat(recomposeCount).isEqualTo(1)
}
}
+
+ @Test
+ @SdkSuppress(minSdkVersion = Build.VERSION_CODES.O)
+ fun zIndexOnItemAffectsDrawingOrder() {
+ rule.setContentWithTestViewConfiguration {
+ LazyVerticalGrid(
+ GridCells.Fixed(1),
+ Modifier.size(6.dp).testTag(LazyGridTag)
+ ) {
+ items(listOf(Color.Blue, Color.Green, Color.Red)) { color ->
+ Box(
+ Modifier
+ .height(2.dp)
+ .width(6.dp)
+ .zIndex(if (color == Color.Green) 1f else 0f)
+ .drawBehind {
+ drawRect(
+ color,
+ topLeft = Offset(-10.dp.toPx(), -10.dp.toPx()),
+ size = Size(20.dp.toPx(), 20.dp.toPx())
+ )
+ })
+ }
+ }
+ }
+
+ rule.onNodeWithTag(LazyGridTag)
+ .captureToImage()
+ .assertPixels { Color.Green }
+ }
}
diff --git a/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/lazy/list/LazyListTest.kt b/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/lazy/list/LazyListTest.kt
index 6192186..d16186e 100644
--- a/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/lazy/list/LazyListTest.kt
+++ b/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/lazy/list/LazyListTest.kt
@@ -42,11 +42,13 @@
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.setValue
import androidx.compose.testutils.WithTouchSlop
+import androidx.compose.testutils.assertPixels
import androidx.compose.testutils.assertShape
import androidx.compose.ui.Modifier
import androidx.compose.ui.composed
import androidx.compose.ui.draw.drawBehind
import androidx.compose.ui.geometry.Offset
+import androidx.compose.ui.geometry.Size
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.RectangleShape
import androidx.compose.ui.layout.layout
@@ -75,6 +77,7 @@
import androidx.compose.ui.unit.Density
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
+import androidx.compose.ui.zIndex
import androidx.test.filters.LargeTest
import androidx.test.filters.SdkSuppress
import com.google.common.collect.Range
@@ -1684,6 +1687,26 @@
}
@Test
+ fun passingNegativeItemsCountIsNotAllowed() {
+ var exception: Exception? = null
+ rule.setContentWithTestViewConfiguration {
+ LazyColumnOrRow {
+ try {
+ items(-1) {
+ Box(Modifier)
+ }
+ } catch (e: Exception) {
+ exception = e
+ }
+ }
+ }
+
+ rule.runOnIdle {
+ assertThat(exception).isInstanceOf(IllegalArgumentException::class.java)
+ }
+ }
+
+ @Test
fun scrollingALotDoesntCauseLazyLayoutRecomposition() {
var recomposeCount = 0
lateinit var state: LazyListState
@@ -1716,6 +1739,35 @@
}
}
+ @Test
+ @SdkSuppress(minSdkVersion = Build.VERSION_CODES.O)
+ fun zIndexOnItemAffectsDrawingOrder() {
+ rule.setContentWithTestViewConfiguration {
+ LazyColumnOrRow(
+ Modifier.size(6.dp).testTag(LazyListTag)
+ ) {
+ items(listOf(Color.Blue, Color.Green, Color.Red)) { color ->
+ Box(
+ Modifier
+ .mainAxisSize(2.dp)
+ .crossAxisSize(6.dp)
+ .zIndex(if (color == Color.Green) 1f else 0f)
+ .drawBehind {
+ drawRect(
+ color,
+ topLeft = Offset(-10.dp.toPx(), -10.dp.toPx()),
+ size = Size(20.dp.toPx(), 20.dp.toPx())
+ )
+ })
+ }
+ }
+ }
+
+ rule.onNodeWithTag(LazyListTag)
+ .captureToImage()
+ .assertPixels { Color.Green }
+ }
+
// ********************* END OF TESTS *********************
// Helper functions, etc. live below here
diff --git a/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/text/CoreTextInlineContentTest.kt b/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/text/CoreTextInlineContentTest.kt
index 0604e3e..38f29ed 100644
--- a/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/text/CoreTextInlineContentTest.kt
+++ b/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/text/CoreTextInlineContentTest.kt
@@ -18,6 +18,7 @@
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.runtime.Composable
import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
@@ -25,17 +26,23 @@
import androidx.compose.ui.Modifier
import androidx.compose.ui.layout.onSizeChanged
import androidx.compose.ui.platform.LocalDensity
+import androidx.compose.ui.platform.LocalLayoutDirection
+import androidx.compose.ui.platform.testTag
import androidx.compose.ui.test.junit4.createComposeRule
+import androidx.compose.ui.test.onNodeWithTag
import androidx.compose.ui.text.Placeholder
import androidx.compose.ui.text.PlaceholderVerticalAlign
import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.text.buildAnnotatedString
+import androidx.compose.ui.text.style.TextDirection
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.Density
import androidx.compose.ui.unit.IntSize
+import androidx.compose.ui.unit.LayoutDirection
import androidx.compose.ui.unit.sp
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.LargeTest
+import com.google.common.truth.Truth.assertThat
import com.nhaarman.mockitokotlin2.mock
import com.nhaarman.mockitokotlin2.verify
import org.junit.Rule
@@ -49,6 +56,10 @@
@get:Rule
val rule = createComposeRule()
+ private val fontSize = 10
+
+ private val textStyle = TextStyle(fontSize = fontSize.sp, fontFamily = TEST_FONT_FAMILY)
+
@Test
fun placeholder_changeSize_updateInlineContentSize() {
// Callback to monitor the size changes of a composable.
@@ -94,4 +105,131 @@
// Verify that the size has been updated to (100, 100).
verify(onSizeChanged).invoke(IntSize(100, 100))
}
+
+ @Test
+ fun rtlLayout_inlineContent_placement() {
+ rule.setContent {
+ CompositionLocalProvider(
+ LocalLayoutDirection provides LayoutDirection.Ltr,
+ ) {
+ // LTR character, supported by sample_font
+ TestContent(
+ predicate = "\u0061\u0061\u0061\u0061\u0061",
+ suffix = "\u0061\u0061\u0061"
+ )
+ }
+ }
+
+ // Expected text layout; "a" is LTR, "b" is RTL"
+ // Text[aaaaa[inline-content]aaa]
+ expectInlineContentPosition(left = fontSize * 5, right = fontSize * 3)
+ }
+
+ @Test
+ fun rtlTextContent_inlineContent_placement() {
+ rule.setContent {
+ // RTL character, supported by sample_font
+ TestContent(
+ predicate = "\u05D1\u05D1\u05D1\u05D1\u05D1",
+ suffix = "\u05D1\u05D1\u05D1"
+ )
+ }
+
+ // Expected text layout; "a" is LTR, "b" is RTL"
+ // Text[bbb[inline-content]bbbbb]
+ expectInlineContentPosition(left = fontSize * 3, right = fontSize * 5)
+ }
+
+ @Test
+ fun rtlTextDirection_inlineContent_placement() {
+ rule.setContent {
+ // LTR character, supported by sample_font
+ TestContent(
+ predicate = "\u0061\u0061\u0061\u0061\u0061",
+ suffix = "\u0061\u0061\u0061",
+ textStyle = textStyle.copy(textDirection = TextDirection.Rtl)
+ )
+ }
+
+ // Expected text layout; "a" is LTR, "b" is RTL"
+ // Text[aaaaa[inline-content]aaa]
+ expectInlineContentPosition(left = fontSize * 5, right = fontSize * 3)
+ }
+
+ @Test
+ fun bidiText_inlineContent_placement() {
+ rule.setContent {
+ // RTL and LTR characters, supported by sample_font
+ TestContent(
+ predicate = "\u05D1\u05D1\u05D1\u0061\u0061",
+ suffix = "\u0061\u0061\u0061"
+ )
+ }
+
+ // Expected text layout; "a" is LTR, "b" is RTL"
+ // Text[bbbaa[inline-content]aaa]
+ expectInlineContentPosition(left = fontSize * 5, right = fontSize * 3)
+ }
+
+ @Test
+ fun bidiText_2_inlineContent_placement() {
+ rule.setContent {
+ // RTL and LTR characters, supported by sample_font
+ TestContent(
+ predicate = "\u0061\u0061\u0061\u05D1\u05D1",
+ suffix = "\u05D1\u05D1\u05D1"
+ )
+ }
+
+ // Expected text layout; "a" is LTR, "b" is RTL"
+ // Text[aaabbb[inline-content]bb]
+ expectInlineContentPosition(left = fontSize * 6, right = fontSize * 2)
+ }
+
+ @Composable
+ private fun TestContent(
+ predicate: String,
+ suffix: String,
+ textStyle: TextStyle = this.textStyle
+ ) {
+ CompositionLocalProvider(
+ LocalDensity provides Density(density = 1f, fontScale = 1f)
+ ) {
+ val inlineTextContent = InlineTextContent(
+ placeholder = Placeholder(
+ fontSize.sp,
+ fontSize.sp,
+ PlaceholderVerticalAlign.AboveBaseline
+ )
+ ) {
+ Box(modifier = Modifier.fillMaxSize().testTag("box"))
+ }
+
+ BasicText(
+ text = buildAnnotatedString {
+ append(predicate)
+ appendInlineContent("box")
+ append(suffix)
+ },
+ modifier = Modifier.testTag("text"),
+ style = textStyle,
+ inlineContent = mapOf("box" to inlineTextContent),
+ maxLines = 1
+ )
+ }
+ }
+
+ private fun expectInlineContentPosition(left: Int, right: Int) {
+ val (boxLeft, boxRight) = with(rule.onNodeWithTag("box").fetchSemanticsNode()) {
+ Pair(positionInRoot.x, positionInRoot.x + size.width)
+ }
+ val (textLeft, textRight) = with(rule.onNodeWithTag("text").fetchSemanticsNode()) {
+ Pair(positionInRoot.x, positionInRoot.x + size.width)
+ }
+
+ rule.waitForIdle()
+
+ assertThat(boxLeft - textLeft).isEqualTo(left)
+ assertThat(textRight - boxRight).isEqualTo(right)
+ }
}
\ No newline at end of file
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/layout/IntervalList.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/layout/IntervalList.kt
index 27d591e..505ec96 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/layout/IntervalList.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/layout/IntervalList.kt
@@ -20,7 +20,12 @@
val startIndex: Int,
val size: Int,
val content: T
-)
+) {
+ init {
+ require(startIndex >= 0) { "startIndex should be non-negative but was $startIndex" }
+ require(size > 0) { "size should be positive but was $size" }
+ }
+}
internal interface IntervalList<T> {
val intervals: List<IntervalHolder<T>>
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/CoreText.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/CoreText.kt
index 5aadf90..1ba93dd 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/CoreText.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/CoreText.kt
@@ -360,8 +360,8 @@
LastBaseline to layoutResult.lastBaseline.roundToInt()
)
) {
- placeables.fastForEach { placeable ->
- placeable.first.placeRelative(placeable.second)
+ placeables.fastForEach { (placeable, position) ->
+ placeable.place(position)
}
}
}
diff --git a/compose/integration-tests/material-catalog/build.gradle b/compose/integration-tests/material-catalog/build.gradle
index 65d3a75..feb57b5 100644
--- a/compose/integration-tests/material-catalog/build.gradle
+++ b/compose/integration-tests/material-catalog/build.gradle
@@ -45,6 +45,7 @@
kotlinPlugin project(":compose:compiler:compiler")
implementation(libs.kotlinStdlib)
implementation project(":compose:runtime:runtime")
+ implementation project(":compose:foundation:foundation-layout")
implementation project(":compose:ui:ui")
implementation project(":compose:material:material")
implementation project(":compose:material3:material3")
@@ -58,7 +59,6 @@
// but it doesn't work in androidx.
// See aosp/1804059
implementation projectOrArtifact(":lifecycle:lifecycle-common-java8")
- implementation "com.google.accompanist:accompanist-insets:0.18.0"
}
// We want to publish a release APK of this project for the Compose Material Catalog
diff --git a/compose/integration-tests/material-catalog/src/main/java/androidx/compose/material/catalog/CatalogApp.kt b/compose/integration-tests/material-catalog/src/main/java/androidx/compose/material/catalog/CatalogApp.kt
index 9f0ae0f..f776c70 100644
--- a/compose/integration-tests/material-catalog/src/main/java/androidx/compose/material/catalog/CatalogApp.kt
+++ b/compose/integration-tests/material-catalog/src/main/java/androidx/compose/material/catalog/CatalogApp.kt
@@ -18,13 +18,10 @@
import androidx.compose.material.catalog.ui.theme.CatalogTheme
import androidx.compose.runtime.Composable
-import com.google.accompanist.insets.ProvideWindowInsets
@Composable
fun CatalogApp() {
- ProvideWindowInsets {
- CatalogTheme {
- NavGraph()
- }
+ CatalogTheme {
+ NavGraph()
}
}
diff --git a/compose/integration-tests/material-catalog/src/main/java/androidx/compose/material/catalog/ui/specification/Specification.kt b/compose/integration-tests/material-catalog/src/main/java/androidx/compose/material/catalog/ui/specification/Specification.kt
index 49dbb09..86be2ef 100644
--- a/compose/integration-tests/material-catalog/src/main/java/androidx/compose/material/catalog/ui/specification/Specification.kt
+++ b/compose/integration-tests/material-catalog/src/main/java/androidx/compose/material/catalog/ui/specification/Specification.kt
@@ -17,10 +17,15 @@
package androidx.compose.material.catalog.ui.specification
import androidx.compose.foundation.ExperimentalFoundationApi
-import androidx.compose.foundation.layout.BoxWithConstraints
import androidx.compose.foundation.layout.Spacer
+import androidx.compose.foundation.layout.WindowInsets
+import androidx.compose.foundation.layout.WindowInsetsSides
+import androidx.compose.foundation.layout.add
+import androidx.compose.foundation.layout.asPaddingValues
import androidx.compose.foundation.layout.height
+import androidx.compose.foundation.layout.only
import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.safeDrawing
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items
import androidx.compose.material.catalog.R
@@ -31,8 +36,6 @@
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
-import com.google.accompanist.insets.LocalWindowInsets
-import com.google.accompanist.insets.rememberInsetsPaddingValues
@Composable
@OptIn(ExperimentalFoundationApi::class)
@@ -43,32 +46,35 @@
SpecificationScaffold(
topBarTitle = stringResource(id = R.string.compose_material_catalog)
) { paddingValues ->
- BoxWithConstraints(modifier = Modifier.padding(paddingValues)) {
- LazyColumn(
- content = {
- item {
- Text(
- text = stringResource(id = R.string.specifications),
- style = MaterialTheme.typography.bodyLarge
- )
- Spacer(modifier = Modifier.height(SpecificationPadding))
- }
- items(specifications) { specification ->
- SpecificationItem(
- specification = specification,
- onClick = onSpecificationClick
- )
- Spacer(modifier = Modifier.height(SpecificationItemPadding))
- }
- },
- contentPadding = rememberInsetsPaddingValues(
- insets = LocalWindowInsets.current.navigationBars,
- additionalTop = SpecificationPadding,
- additionalStart = SpecificationPadding,
- additionalEnd = SpecificationPadding
+ LazyColumn(
+ content = {
+ item {
+ Text(
+ text = stringResource(id = R.string.specifications),
+ style = MaterialTheme.typography.bodyLarge
+ )
+ Spacer(modifier = Modifier.height(SpecificationPadding))
+ }
+ items(specifications) { specification ->
+ SpecificationItem(
+ specification = specification,
+ onClick = onSpecificationClick
+ )
+ Spacer(modifier = Modifier.height(SpecificationItemPadding))
+ }
+ },
+ contentPadding = WindowInsets.safeDrawing
+ .only(WindowInsetsSides.Horizontal + WindowInsetsSides.Top)
+ .add(
+ WindowInsets(
+ left = SpecificationPadding,
+ top = SpecificationPadding,
+ right = SpecificationPadding,
+ )
)
- )
- }
+ .asPaddingValues(),
+ modifier = Modifier.padding(paddingValues)
+ )
}
}
diff --git a/compose/integration-tests/material-catalog/src/main/java/androidx/compose/material/catalog/ui/specification/SpecificationTopAppBar.kt b/compose/integration-tests/material-catalog/src/main/java/androidx/compose/material/catalog/ui/specification/SpecificationTopAppBar.kt
index 313f65b..e22a36e 100644
--- a/compose/integration-tests/material-catalog/src/main/java/androidx/compose/material/catalog/ui/specification/SpecificationTopAppBar.kt
+++ b/compose/integration-tests/material-catalog/src/main/java/androidx/compose/material/catalog/ui/specification/SpecificationTopAppBar.kt
@@ -16,6 +16,11 @@
package androidx.compose.material.catalog.ui.specification
+import androidx.compose.foundation.layout.WindowInsets
+import androidx.compose.foundation.layout.WindowInsetsSides
+import androidx.compose.foundation.layout.only
+import androidx.compose.foundation.layout.safeDrawing
+import androidx.compose.foundation.layout.windowInsetsPadding
import androidx.compose.material3.CenterAlignedTopAppBar
import androidx.compose.material3.Surface
import androidx.compose.material3.Text
@@ -25,8 +30,6 @@
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.text.style.TextOverflow
-import com.google.accompanist.insets.navigationBarsPadding
-import com.google.accompanist.insets.statusBarsPadding
@Composable
fun SpecificationTopAppBar(
@@ -54,9 +57,9 @@
},
scrollBehavior = scrollBehavior,
colors = foregroundColors,
- modifier = Modifier
- .statusBarsPadding()
- .navigationBarsPadding(bottom = false)
+ modifier = Modifier.windowInsetsPadding(
+ WindowInsets.safeDrawing.only(WindowInsetsSides.Horizontal + WindowInsetsSides.Top)
+ )
)
}
}
diff --git a/compose/lint/internal-lint-checks/src/main/java/androidx/compose/lint/ModifierInspectorInfoDetector.kt b/compose/lint/internal-lint-checks/src/main/java/androidx/compose/lint/ModifierInspectorInfoDetector.kt
index 6483d90..23f9a14 100644
--- a/compose/lint/internal-lint-checks/src/main/java/androidx/compose/lint/ModifierInspectorInfoDetector.kt
+++ b/compose/lint/internal-lint-checks/src/main/java/androidx/compose/lint/ModifierInspectorInfoDetector.kt
@@ -501,14 +501,15 @@
*/
private inner class ModifierVisitor : UnexpectedVisitor({ wrongLambda(it) }) {
override fun visitCallExpression(node: UCallExpression): Boolean {
- val info = if (isInspectableModifier(node)) node.valueArguments.firstOrNull()
- else node.valueArguments.lastOrNull()
- if (isInspectorInfoLambdaType(info?.getExpressionType())) {
- info!!.accept(debugInspectorVisitor)
+ val info: UExpression? = node.valueArguments.firstOrNull {
+ isInspectorInfoLambdaType(it.getExpressionType())
+ }
+ if (info != null) {
+ info.accept(debugInspectorVisitor)
return true
}
if (isRememberFunctionCall(node)) {
- val lambda = info as? ULambdaExpression
+ val lambda = node.valueArguments.singleOrNull() as? ULambdaExpression
val body = lambda?.body as? UBlockExpression
val ret = body?.expressions?.firstOrNull() as? UReturnExpression
val definition = ret?.returnExpression ?: return super.visitCallExpression(node)
diff --git a/compose/lint/internal-lint-checks/src/test/java/androidx/compose/lint/ModifierInspectorInfoDetectorTest.kt b/compose/lint/internal-lint-checks/src/test/java/androidx/compose/lint/ModifierInspectorInfoDetectorTest.kt
index 60a2359..ab8faf7 100644
--- a/compose/lint/internal-lint-checks/src/test/java/androidx/compose/lint/ModifierInspectorInfoDetectorTest.kt
+++ b/compose/lint/internal-lint-checks/src/test/java/androidx/compose/lint/ModifierInspectorInfoDetectorTest.kt
@@ -1397,4 +1397,42 @@
"""
)
}
+
+ @Test
+ fun passInspectorInfoAtSecondLastParameter() {
+ lint().files(
+ Stubs.Modifier,
+ composedStub,
+ inspectableInfoStub,
+ kotlin(
+ """
+ package androidx.compose.ui
+
+ import androidx.compose.ui.Modifier
+ import androidx.compose.ui.platform.InspectorInfo
+ import androidx.compose.ui.platform.InspectorValueInfo
+ import androidx.compose.ui.platform.debugInspectorInfo
+
+ inline class Dp(val value: Float)
+
+ fun Modifier.width1(width: Dp, height: Dp) =
+ this.then(SizeModifier1(width, inspectorInfo = debugInspectorInfo {
+ name = "width1"
+ properties["width"] = width
+ properties["height"] = height
+ }, height))
+
+ private class SizeModifier1(
+ val width: Dp,
+ inspectorInfo: InspectorInfo.() -> Unit,
+ val height: Dp
+ ): Modifier.Element, InspectorValueInfo(inspectorInfo)
+
+ """
+ ).indented()
+ )
+ .testModes(TestMode.DEFAULT)
+ .run()
+ .expectClean()
+ }
}
diff --git a/compose/material/material/integration-tests/material-catalog/build.gradle b/compose/material/material/integration-tests/material-catalog/build.gradle
index e7d62f0..c7aa189 100644
--- a/compose/material/material/integration-tests/material-catalog/build.gradle
+++ b/compose/material/material/integration-tests/material-catalog/build.gradle
@@ -29,11 +29,11 @@
implementation(libs.kotlinStdlib)
implementation project(":core:core")
implementation project(":compose:runtime:runtime")
+ implementation project(":compose:foundation:foundation-layout")
implementation project(":compose:ui:ui")
implementation project(":compose:material:material")
implementation project(":compose:material:material:material-samples")
implementation project(":navigation:navigation-compose")
- implementation "com.google.accompanist:accompanist-insets:0.18.0"
}
androidx {
diff --git a/compose/material/material/integration-tests/material-catalog/src/main/java/androidx/compose/material/catalog/library/ui/common/CatalogTopAppBar.kt b/compose/material/material/integration-tests/material-catalog/src/main/java/androidx/compose/material/catalog/library/ui/common/CatalogTopAppBar.kt
index 6132c51..80688a3 100644
--- a/compose/material/material/integration-tests/material-catalog/src/main/java/androidx/compose/material/catalog/library/ui/common/CatalogTopAppBar.kt
+++ b/compose/material/material/integration-tests/material-catalog/src/main/java/androidx/compose/material/catalog/library/ui/common/CatalogTopAppBar.kt
@@ -18,6 +18,11 @@
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.WindowInsets
+import androidx.compose.foundation.layout.WindowInsetsSides
+import androidx.compose.foundation.layout.only
+import androidx.compose.foundation.layout.safeDrawing
+import androidx.compose.foundation.layout.windowInsetsPadding
import androidx.compose.material.AppBarDefaults
import androidx.compose.material.Divider
import androidx.compose.material.DropdownMenu
@@ -44,8 +49,6 @@
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.dp
-import com.google.accompanist.insets.navigationBarsPadding
-import com.google.accompanist.insets.statusBarsPadding
@Composable
fun CatalogTopAppBar(
@@ -140,9 +143,9 @@
},
backgroundColor = Color.Transparent,
elevation = 0.dp,
- modifier = Modifier
- .statusBarsPadding()
- .navigationBarsPadding(bottom = false)
+ modifier = Modifier.windowInsetsPadding(
+ WindowInsets.safeDrawing.only(WindowInsetsSides.Horizontal + WindowInsetsSides.Top)
+ )
)
}
}
diff --git a/compose/material/material/integration-tests/material-catalog/src/main/java/androidx/compose/material/catalog/library/ui/component/Component.kt b/compose/material/material/integration-tests/material-catalog/src/main/java/androidx/compose/material/catalog/library/ui/component/Component.kt
index 791dfa8..d311df8 100644
--- a/compose/material/material/integration-tests/material-catalog/src/main/java/androidx/compose/material/catalog/library/ui/component/Component.kt
+++ b/compose/material/material/integration-tests/material-catalog/src/main/java/androidx/compose/material/catalog/library/ui/component/Component.kt
@@ -19,9 +19,14 @@
import androidx.compose.foundation.Image
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Spacer
+import androidx.compose.foundation.layout.WindowInsets
+import androidx.compose.foundation.layout.WindowInsetsSides
+import androidx.compose.foundation.layout.asPaddingValues
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
+import androidx.compose.foundation.layout.only
import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.safeDrawing
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items
@@ -42,8 +47,6 @@
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
-import com.google.accompanist.insets.LocalWindowInsets
-import com.google.accompanist.insets.rememberInsetsPaddingValues
@Composable
fun Component(
@@ -67,9 +70,9 @@
modifier = Modifier
.padding(paddingValues)
.padding(horizontal = ComponentPadding),
- contentPadding = rememberInsetsPaddingValues(
- insets = LocalWindowInsets.current.navigationBars
- )
+ contentPadding = WindowInsets.safeDrawing
+ .only(WindowInsetsSides.Horizontal + WindowInsetsSides.Bottom)
+ .asPaddingValues()
) {
item {
Box(
diff --git a/compose/material/material/integration-tests/material-catalog/src/main/java/androidx/compose/material/catalog/library/ui/example/Example.kt b/compose/material/material/integration-tests/material-catalog/src/main/java/androidx/compose/material/catalog/library/ui/example/Example.kt
index d6021c3..31fc61d 100644
--- a/compose/material/material/integration-tests/material-catalog/src/main/java/androidx/compose/material/catalog/library/ui/example/Example.kt
+++ b/compose/material/material/integration-tests/material-catalog/src/main/java/androidx/compose/material/catalog/library/ui/example/Example.kt
@@ -17,8 +17,13 @@
package androidx.compose.material.catalog.library.ui.example
import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.WindowInsets
+import androidx.compose.foundation.layout.WindowInsetsSides
import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.layout.only
import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.safeDrawing
+import androidx.compose.foundation.layout.windowInsetsPadding
import androidx.compose.material.catalog.library.model.Component
import androidx.compose.material.catalog.library.model.Example
import androidx.compose.material.catalog.library.model.Theme
@@ -26,7 +31,6 @@
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
-import com.google.accompanist.insets.navigationBarsPadding
@Composable
fun Example(
@@ -50,7 +54,10 @@
modifier = Modifier
.fillMaxSize()
.padding(paddingValues)
- .navigationBarsPadding(),
+ .windowInsetsPadding(
+ WindowInsets.safeDrawing
+ .only(WindowInsetsSides.Horizontal + WindowInsetsSides.Bottom)
+ ),
contentAlignment = Alignment.Center
) {
example.content()
diff --git a/compose/material/material/integration-tests/material-catalog/src/main/java/androidx/compose/material/catalog/library/ui/home/Home.kt b/compose/material/material/integration-tests/material-catalog/src/main/java/androidx/compose/material/catalog/library/ui/home/Home.kt
index ff77567..9a23446 100644
--- a/compose/material/material/integration-tests/material-catalog/src/main/java/androidx/compose/material/catalog/library/ui/home/Home.kt
+++ b/compose/material/material/integration-tests/material-catalog/src/main/java/androidx/compose/material/catalog/library/ui/home/Home.kt
@@ -18,7 +18,12 @@
import androidx.compose.foundation.ExperimentalFoundationApi
import androidx.compose.foundation.layout.BoxWithConstraints
+import androidx.compose.foundation.layout.WindowInsets
+import androidx.compose.foundation.layout.WindowInsetsSides
+import androidx.compose.foundation.layout.asPaddingValues
+import androidx.compose.foundation.layout.only
import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.safeDrawing
import androidx.compose.foundation.lazy.GridCells
import androidx.compose.foundation.lazy.LazyVerticalGrid
import androidx.compose.foundation.lazy.itemsIndexed
@@ -31,8 +36,6 @@
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
-import com.google.accompanist.insets.LocalWindowInsets
-import com.google.accompanist.insets.rememberInsetsPaddingValues
@Composable
@OptIn(ExperimentalFoundationApi::class)
@@ -63,9 +66,9 @@
)
}
},
- contentPadding = rememberInsetsPaddingValues(
- insets = LocalWindowInsets.current.navigationBars
- )
+ contentPadding = WindowInsets.safeDrawing
+ .only(WindowInsetsSides.Horizontal + WindowInsetsSides.Bottom)
+ .asPaddingValues()
)
}
}
diff --git a/compose/material/material/integration-tests/material-catalog/src/main/java/androidx/compose/material/catalog/library/ui/theme/ThemePicker.kt b/compose/material/material/integration-tests/material-catalog/src/main/java/androidx/compose/material/catalog/library/ui/theme/ThemePicker.kt
index 48c1770..c8a0638 100644
--- a/compose/material/material/integration-tests/material-catalog/src/main/java/androidx/compose/material/catalog/library/ui/theme/ThemePicker.kt
+++ b/compose/material/material/integration-tests/material-catalog/src/main/java/androidx/compose/material/catalog/library/ui/theme/ThemePicker.kt
@@ -20,9 +20,15 @@
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
+import androidx.compose.foundation.layout.WindowInsets
+import androidx.compose.foundation.layout.WindowInsetsSides
+import androidx.compose.foundation.layout.add
+import androidx.compose.foundation.layout.asPaddingValues
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
+import androidx.compose.foundation.layout.only
import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.safeDrawing
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.LazyRow
@@ -60,8 +66,6 @@
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
-import com.google.accompanist.insets.LocalWindowInsets
-import com.google.accompanist.insets.rememberInsetsPaddingValues
@Composable
fun ThemePicker(
@@ -69,12 +73,17 @@
onThemeChange: (theme: Theme) -> Unit
) {
var themeState by remember { mutableStateOf(theme) }
+
LazyColumn(
- contentPadding = rememberInsetsPaddingValues(
- insets = LocalWindowInsets.current.navigationBars,
- additionalTop = ThemePickerPadding,
- additionalBottom = ThemePickerPadding
- )
+ contentPadding = WindowInsets.safeDrawing
+ .only(WindowInsetsSides.Horizontal + WindowInsetsSides.Bottom)
+ .add(
+ WindowInsets(
+ top = ThemePickerPadding,
+ bottom = ThemePickerPadding
+ )
+ )
+ .asPaddingValues()
) {
item {
Text(
diff --git a/compose/material3/material3/api/public_plus_experimental_current.txt b/compose/material3/material3/api/public_plus_experimental_current.txt
index 7ea8041..358f8cd 100644
--- a/compose/material3/material3/api/public_plus_experimental_current.txt
+++ b/compose/material3/material3/api/public_plus_experimental_current.txt
@@ -188,9 +188,13 @@
}
@androidx.compose.material3.ExperimentalMaterial3Api public final class DrawerDefaults {
- method public float getElevation();
+ method public float getDismissibleDrawerElevation();
+ method public float getModalDrawerElevation();
+ method public float getPermanentDrawerElevation();
method @androidx.compose.runtime.Composable public long getScrimColor();
- property public final float Elevation;
+ property public final float DismissibleDrawerElevation;
+ property public final float ModalDrawerElevation;
+ property public final float PermanentDrawerElevation;
property @androidx.compose.runtime.Composable public final long scrimColor;
field public static final androidx.compose.material3.DrawerDefaults INSTANCE;
}
@@ -323,8 +327,26 @@
method @androidx.compose.runtime.Composable public static void NavigationBarItem(androidx.compose.foundation.layout.RowScope, boolean selected, kotlin.jvm.functions.Function0<kotlin.Unit> onClick, kotlin.jvm.functions.Function0<kotlin.Unit> icon, optional androidx.compose.ui.Modifier modifier, optional boolean enabled, optional kotlin.jvm.functions.Function0<kotlin.Unit>? label, optional boolean alwaysShowLabel, optional androidx.compose.foundation.interaction.MutableInteractionSource interactionSource, optional androidx.compose.material3.NavigationBarItemColors colors);
}
+ @androidx.compose.material3.ExperimentalMaterial3Api @androidx.compose.runtime.Stable public interface NavigationDrawerItemColors {
+ method @androidx.compose.runtime.Composable public androidx.compose.runtime.State<androidx.compose.ui.graphics.Color> badgeColor(boolean selected);
+ method @androidx.compose.runtime.Composable public androidx.compose.runtime.State<androidx.compose.ui.graphics.Color> containerColor(boolean selected);
+ method @androidx.compose.runtime.Composable public androidx.compose.runtime.State<androidx.compose.ui.graphics.Color> iconColor(boolean selected);
+ method @androidx.compose.runtime.Composable public androidx.compose.runtime.State<androidx.compose.ui.graphics.Color> textColor(boolean selected);
+ }
+
+ @androidx.compose.material3.ExperimentalMaterial3Api public final class NavigationDrawerItemDefaults {
+ method @androidx.compose.runtime.Composable public androidx.compose.material3.NavigationDrawerItemColors colors(optional long selectedContainerColor, optional long unselectedContainerColor, optional long selectedIconColor, optional long unselectedIconColor, optional long selectedTextColor, optional long unselectedTextColor, optional long selectedBadgeColor, optional long unselectedBadgeColor);
+ method public androidx.compose.foundation.layout.PaddingValues getItemPadding();
+ property public final androidx.compose.foundation.layout.PaddingValues ItemPadding;
+ field public static final androidx.compose.material3.NavigationDrawerItemDefaults INSTANCE;
+ }
+
public final class NavigationDrawerKt {
- method @androidx.compose.material3.ExperimentalMaterial3Api @androidx.compose.runtime.Composable public static void NavigationDrawer(kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.ColumnScope,kotlin.Unit> drawerContent, optional androidx.compose.ui.Modifier modifier, optional androidx.compose.material3.DrawerState drawerState, optional boolean gesturesEnabled, optional androidx.compose.ui.graphics.Shape drawerShape, optional float drawerTonalElevation, optional long drawerContainerColor, optional long drawerContentColor, optional long scrimColor, kotlin.jvm.functions.Function0<kotlin.Unit> content);
+ method @androidx.compose.material3.ExperimentalMaterial3Api @androidx.compose.runtime.Composable public static void DismissibleNavigationDrawer(kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.ColumnScope,kotlin.Unit> drawerContent, optional androidx.compose.ui.Modifier modifier, optional androidx.compose.material3.DrawerState drawerState, optional boolean gesturesEnabled, optional androidx.compose.ui.graphics.Shape drawerShape, optional float drawerTonalElevation, optional long drawerContainerColor, optional long drawerContentColor, kotlin.jvm.functions.Function0<kotlin.Unit> content);
+ method @androidx.compose.material3.ExperimentalMaterial3Api @androidx.compose.runtime.Composable public static void ModalNavigationDrawer(kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.ColumnScope,kotlin.Unit> drawerContent, optional androidx.compose.ui.Modifier modifier, optional androidx.compose.material3.DrawerState drawerState, optional boolean gesturesEnabled, optional androidx.compose.ui.graphics.Shape drawerShape, optional float drawerTonalElevation, optional long drawerContainerColor, optional long drawerContentColor, optional long scrimColor, kotlin.jvm.functions.Function0<kotlin.Unit> content);
+ method @Deprecated @androidx.compose.material3.ExperimentalMaterial3Api @androidx.compose.runtime.Composable public static void NavigationDrawer(kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.ColumnScope,kotlin.Unit> drawerContent, optional androidx.compose.ui.Modifier modifier, optional androidx.compose.material3.DrawerState drawerState, optional boolean gesturesEnabled, optional androidx.compose.ui.graphics.Shape drawerShape, optional float drawerTonalElevation, optional long drawerContainerColor, optional long drawerContentColor, optional long scrimColor, kotlin.jvm.functions.Function0<kotlin.Unit> content);
+ method @androidx.compose.material3.ExperimentalMaterial3Api @androidx.compose.runtime.Composable public static void NavigationDrawerItem(kotlin.jvm.functions.Function0<kotlin.Unit> label, boolean selected, kotlin.jvm.functions.Function0<kotlin.Unit> onClick, optional androidx.compose.ui.Modifier modifier, optional kotlin.jvm.functions.Function0<kotlin.Unit>? icon, optional kotlin.jvm.functions.Function0<kotlin.Unit>? badge, optional androidx.compose.ui.graphics.Shape shape, optional androidx.compose.material3.NavigationDrawerItemColors colors, optional androidx.compose.foundation.interaction.MutableInteractionSource interactionSource);
+ method @androidx.compose.material3.ExperimentalMaterial3Api @androidx.compose.runtime.Composable public static void PermanentNavigationDrawer(kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.ColumnScope,kotlin.Unit> drawerContent, optional androidx.compose.ui.Modifier modifier, optional androidx.compose.ui.graphics.Shape drawerShape, optional float drawerTonalElevation, optional long drawerContainerColor, optional long drawerContentColor, kotlin.jvm.functions.Function0<kotlin.Unit> content);
method @androidx.compose.material3.ExperimentalMaterial3Api @androidx.compose.runtime.Composable public static androidx.compose.material3.DrawerState rememberDrawerState(androidx.compose.material3.DrawerValue initialValue, optional kotlin.jvm.functions.Function1<? super androidx.compose.material3.DrawerValue,java.lang.Boolean> confirmStateChange);
}
diff --git a/compose/material3/material3/integration-tests/material3-catalog/build.gradle b/compose/material3/material3/integration-tests/material3-catalog/build.gradle
index b2e7e22..77f1d0b 100644
--- a/compose/material3/material3/integration-tests/material3-catalog/build.gradle
+++ b/compose/material3/material3/integration-tests/material3-catalog/build.gradle
@@ -28,12 +28,12 @@
implementation(libs.kotlinStdlib)
implementation project(":core:core")
implementation project(":compose:runtime:runtime")
+ implementation project(":compose:foundation:foundation-layout")
implementation project(":compose:ui:ui")
implementation project(":compose:material:material")
implementation project(":compose:material3:material3")
implementation project(":compose:material3:material3:material3-samples")
implementation project(":navigation:navigation-compose")
- implementation "com.google.accompanist:accompanist-insets:0.18.0"
}
androidx {
diff --git a/compose/material3/material3/integration-tests/material3-catalog/src/main/java/androidx/compose/material3/catalog/library/model/Examples.kt b/compose/material3/material3/integration-tests/material3-catalog/src/main/java/androidx/compose/material3/catalog/library/model/Examples.kt
index da40a13..ffa7dc5 100644
--- a/compose/material3/material3/integration-tests/material3-catalog/src/main/java/androidx/compose/material3/catalog/library/model/Examples.kt
+++ b/compose/material3/material3/integration-tests/material3-catalog/src/main/java/androidx/compose/material3/catalog/library/model/Examples.kt
@@ -31,6 +31,7 @@
import androidx.compose.material3.samples.ClickableElevatedCardSample
import androidx.compose.material3.samples.ClickableOutlinedCardSample
import androidx.compose.material3.samples.ColorSchemeSample
+import androidx.compose.material3.samples.DismissibleNavigationDrawerSample
import androidx.compose.material3.samples.ElevatedButtonSample
import androidx.compose.material3.samples.ElevatedCardSample
import androidx.compose.material3.samples.EnterAlwaysSmallTopAppBar
@@ -49,12 +50,13 @@
import androidx.compose.material3.samples.MenuSample
import androidx.compose.material3.samples.NavigationBarSample
import androidx.compose.material3.samples.NavigationBarWithOnlySelectedLabelsSample
-import androidx.compose.material3.samples.NavigationDrawerSample
+import androidx.compose.material3.samples.ModalNavigationDrawerSample
import androidx.compose.material3.samples.NavigationRailBottomAlignSample
import androidx.compose.material3.samples.NavigationRailSample
import androidx.compose.material3.samples.NavigationRailWithOnlySelectedLabelsSample
import androidx.compose.material3.samples.OutlinedButtonSample
import androidx.compose.material3.samples.OutlinedCardSample
+import androidx.compose.material3.samples.PermanentNavigationDrawerSample
import androidx.compose.material3.samples.PinnedSmallTopAppBar
import androidx.compose.material3.samples.RadioButtonSample
import androidx.compose.material3.samples.RadioGroupSample
@@ -349,11 +351,25 @@
private const val NavigationDrawerExampleSourceUrl = "$SampleSourceUrl/DrawerSamples.kt"
val NavigationDrawerExamples = listOf(
Example(
- name = ::NavigationDrawerSample.name,
+ name = ::ModalNavigationDrawerSample.name,
description = NavigationDrawerExampleDescription,
sourceUrl = NavigationDrawerExampleSourceUrl
) {
- NavigationDrawerSample()
+ ModalNavigationDrawerSample()
+ },
+ Example(
+ name = ::PermanentNavigationDrawerSample.name,
+ description = NavigationDrawerExampleDescription,
+ sourceUrl = NavigationDrawerExampleSourceUrl
+ ) {
+ PermanentNavigationDrawerSample()
+ },
+ Example(
+ name = ::DismissibleNavigationDrawerSample.name,
+ description = NavigationDrawerExampleDescription,
+ sourceUrl = NavigationDrawerExampleSourceUrl
+ ) {
+ DismissibleNavigationDrawerSample()
}
)
diff --git a/compose/material3/material3/integration-tests/material3-catalog/src/main/java/androidx/compose/material3/catalog/library/ui/common/CatalogTopAppBar.kt b/compose/material3/material3/integration-tests/material3-catalog/src/main/java/androidx/compose/material3/catalog/library/ui/common/CatalogTopAppBar.kt
index 14429a22..9cb326c 100644
--- a/compose/material3/material3/integration-tests/material3-catalog/src/main/java/androidx/compose/material3/catalog/library/ui/common/CatalogTopAppBar.kt
+++ b/compose/material3/material3/integration-tests/material3-catalog/src/main/java/androidx/compose/material3/catalog/library/ui/common/CatalogTopAppBar.kt
@@ -18,6 +18,11 @@
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.WindowInsets
+import androidx.compose.foundation.layout.WindowInsetsSides
+import androidx.compose.foundation.layout.only
+import androidx.compose.foundation.layout.safeDrawing
+import androidx.compose.foundation.layout.windowInsetsPadding
import androidx.compose.material.Divider
import androidx.compose.material.DropdownMenu
import androidx.compose.material.DropdownMenuItem
@@ -43,8 +48,6 @@
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.style.TextOverflow
-import com.google.accompanist.insets.navigationBarsPadding
-import com.google.accompanist.insets.statusBarsPadding
@Composable
fun CatalogTopAppBar(
@@ -143,9 +146,11 @@
},
scrollBehavior = scrollBehavior,
colors = foregroundColors,
- modifier = Modifier
- .statusBarsPadding()
- .navigationBarsPadding(bottom = false)
+ modifier = Modifier.windowInsetsPadding(
+ WindowInsets.safeDrawing.only(
+ WindowInsetsSides.Horizontal + WindowInsetsSides.Top
+ )
+ )
)
}
}
diff --git a/compose/material3/material3/integration-tests/material3-catalog/src/main/java/androidx/compose/material3/catalog/library/ui/component/Component.kt b/compose/material3/material3/integration-tests/material3-catalog/src/main/java/androidx/compose/material3/catalog/library/ui/component/Component.kt
index b37f34a..8b16664 100644
--- a/compose/material3/material3/integration-tests/material3-catalog/src/main/java/androidx/compose/material3/catalog/library/ui/component/Component.kt
+++ b/compose/material3/material3/integration-tests/material3-catalog/src/main/java/androidx/compose/material3/catalog/library/ui/component/Component.kt
@@ -19,9 +19,15 @@
import androidx.compose.foundation.Image
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Spacer
+import androidx.compose.foundation.layout.WindowInsets
+import androidx.compose.foundation.layout.WindowInsetsSides
+import androidx.compose.foundation.layout.add
+import androidx.compose.foundation.layout.asPaddingValues
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
+import androidx.compose.foundation.layout.only
import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.safeDrawing
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items
@@ -41,8 +47,6 @@
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
-import com.google.accompanist.insets.LocalWindowInsets
-import com.google.accompanist.insets.rememberInsetsPaddingValues
@Composable
fun Component(
@@ -64,11 +68,15 @@
) { paddingValues ->
LazyColumn(
modifier = Modifier.padding(paddingValues),
- contentPadding = rememberInsetsPaddingValues(
- insets = LocalWindowInsets.current.navigationBars,
- additionalStart = ComponentPadding,
- additionalEnd = ComponentPadding
- )
+ contentPadding = WindowInsets.safeDrawing
+ .only(WindowInsetsSides.Horizontal + WindowInsetsSides.Bottom)
+ .add(
+ WindowInsets(
+ left = ComponentPadding,
+ right = ComponentPadding
+ )
+ )
+ .asPaddingValues()
) {
item {
Box(
diff --git a/compose/material3/material3/integration-tests/material3-catalog/src/main/java/androidx/compose/material3/catalog/library/ui/example/Example.kt b/compose/material3/material3/integration-tests/material3-catalog/src/main/java/androidx/compose/material3/catalog/library/ui/example/Example.kt
index 43dc1b6..4eefa74 100644
--- a/compose/material3/material3/integration-tests/material3-catalog/src/main/java/androidx/compose/material3/catalog/library/ui/example/Example.kt
+++ b/compose/material3/material3/integration-tests/material3-catalog/src/main/java/androidx/compose/material3/catalog/library/ui/example/Example.kt
@@ -17,8 +17,13 @@
package androidx.compose.material3.catalog.library.ui.example
import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.WindowInsets
+import androidx.compose.foundation.layout.WindowInsetsSides
import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.layout.only
import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.safeDrawing
+import androidx.compose.foundation.layout.windowInsetsPadding
import androidx.compose.material3.catalog.library.model.Component
import androidx.compose.material3.catalog.library.model.Example
import androidx.compose.material3.catalog.library.model.Theme
@@ -26,7 +31,6 @@
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
-import com.google.accompanist.insets.navigationBarsPadding
@Composable
fun Example(
@@ -50,7 +54,10 @@
modifier = Modifier
.fillMaxSize()
.padding(paddingValues)
- .navigationBarsPadding(),
+ .windowInsetsPadding(
+ WindowInsets.safeDrawing
+ .only(WindowInsetsSides.Horizontal + WindowInsetsSides.Bottom)
+ ),
contentAlignment = Alignment.Center
) {
example.content()
diff --git a/compose/material3/material3/integration-tests/material3-catalog/src/main/java/androidx/compose/material3/catalog/library/ui/home/Home.kt b/compose/material3/material3/integration-tests/material3-catalog/src/main/java/androidx/compose/material3/catalog/library/ui/home/Home.kt
index db3c713..ae054b0 100644
--- a/compose/material3/material3/integration-tests/material3-catalog/src/main/java/androidx/compose/material3/catalog/library/ui/home/Home.kt
+++ b/compose/material3/material3/integration-tests/material3-catalog/src/main/java/androidx/compose/material3/catalog/library/ui/home/Home.kt
@@ -17,8 +17,13 @@
package androidx.compose.material3.catalog.library.ui.home
import androidx.compose.foundation.ExperimentalFoundationApi
-import androidx.compose.foundation.layout.BoxWithConstraints
+import androidx.compose.foundation.layout.WindowInsets
+import androidx.compose.foundation.layout.WindowInsetsSides
+import androidx.compose.foundation.layout.add
+import androidx.compose.foundation.layout.asPaddingValues
+import androidx.compose.foundation.layout.only
import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.safeDrawing
import androidx.compose.foundation.lazy.GridCells
import androidx.compose.foundation.lazy.LazyVerticalGrid
import androidx.compose.foundation.lazy.items
@@ -31,8 +36,6 @@
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
-import com.google.accompanist.insets.LocalWindowInsets
-import com.google.accompanist.insets.rememberInsetsPaddingValues
@Composable
@OptIn(ExperimentalFoundationApi::class)
@@ -47,27 +50,29 @@
theme = theme,
onThemeChange = onThemeChange
) { paddingValues ->
- BoxWithConstraints(modifier = Modifier.padding(paddingValues)) {
- LazyVerticalGrid(
- modifier = Modifier.padding(paddingValues),
- cells = GridCells.Adaptive(HomeCellMinSize),
- content = {
- items(components) { component ->
- ComponentItem(
- component = component,
- onClick = onComponentClick
- )
- }
- },
- contentPadding = rememberInsetsPaddingValues(
- insets = LocalWindowInsets.current.navigationBars,
- additionalStart = HomePadding,
- additionalTop = HomePadding,
- additionalEnd = HomePadding,
- additionalBottom = HomePadding
+ LazyVerticalGrid(
+ modifier = Modifier.padding(paddingValues),
+ cells = GridCells.Adaptive(HomeCellMinSize),
+ content = {
+ items(components) { component ->
+ ComponentItem(
+ component = component,
+ onClick = onComponentClick
+ )
+ }
+ },
+ contentPadding = WindowInsets.safeDrawing
+ .only(WindowInsetsSides.Horizontal + WindowInsetsSides.Bottom)
+ .add(
+ WindowInsets(
+ left = HomePadding,
+ top = HomePadding,
+ right = HomePadding,
+ bottom = HomePadding
+ )
)
- )
- }
+ .asPaddingValues()
+ )
}
}
diff --git a/compose/material3/material3/integration-tests/material3-catalog/src/main/java/androidx/compose/material3/catalog/library/ui/theme/ThemePicker.kt b/compose/material3/material3/integration-tests/material3-catalog/src/main/java/androidx/compose/material3/catalog/library/ui/theme/ThemePicker.kt
index 11fd26d..7a01f2b 100644
--- a/compose/material3/material3/integration-tests/material3-catalog/src/main/java/androidx/compose/material3/catalog/library/ui/theme/ThemePicker.kt
+++ b/compose/material3/material3/integration-tests/material3-catalog/src/main/java/androidx/compose/material3/catalog/library/ui/theme/ThemePicker.kt
@@ -20,8 +20,14 @@
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.WindowInsets
+import androidx.compose.foundation.layout.WindowInsetsSides
+import androidx.compose.foundation.layout.add
+import androidx.compose.foundation.layout.asPaddingValues
import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.only
import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.safeDrawing
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.material.Divider
import androidx.compose.material.RadioButton
@@ -46,8 +52,6 @@
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
-import com.google.accompanist.insets.LocalWindowInsets
-import com.google.accompanist.insets.rememberInsetsPaddingValues
@Composable
fun ThemePicker(
@@ -55,11 +59,15 @@
onThemeChange: (theme: Theme) -> Unit
) {
LazyColumn(
- contentPadding = rememberInsetsPaddingValues(
- insets = LocalWindowInsets.current.navigationBars,
- additionalTop = ThemePickerPadding,
- additionalBottom = ThemePickerPadding
- ),
+ contentPadding = WindowInsets.safeDrawing
+ .only(WindowInsetsSides.Horizontal + WindowInsetsSides.Bottom)
+ .add(
+ WindowInsets(
+ top = ThemePickerPadding,
+ bottom = ThemePickerPadding
+ )
+ )
+ .asPaddingValues(),
verticalArrangement = Arrangement.spacedBy(ThemePickerPadding)
) {
item {
diff --git a/compose/material3/material3/samples/src/main/java/androidx/compose/material3/samples/DrawerSamples.kt b/compose/material3/material3/samples/src/main/java/androidx/compose/material3/samples/DrawerSamples.kt
index 3257fb5..58c4182 100644
--- a/compose/material3/material3/samples/src/main/java/androidx/compose/material3/samples/DrawerSamples.kt
+++ b/compose/material3/material3/samples/src/main/java/androidx/compose/material3/samples/DrawerSamples.kt
@@ -22,13 +22,24 @@
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
+import androidx.compose.material.icons.Icons
+import androidx.compose.material.icons.filled.Email
+import androidx.compose.material.icons.filled.Face
+import androidx.compose.material.icons.filled.Favorite
import androidx.compose.material3.Button
+import androidx.compose.material3.DismissibleNavigationDrawer
+import androidx.compose.material3.NavigationDrawerItem
import androidx.compose.material3.DrawerValue
import androidx.compose.material3.ExperimentalMaterial3Api
-import androidx.compose.material3.NavigationDrawer
+import androidx.compose.material3.Icon
+import androidx.compose.material3.ModalNavigationDrawer
+import androidx.compose.material3.NavigationDrawerItemDefaults
+import androidx.compose.material3.PermanentNavigationDrawer
import androidx.compose.material3.Text
import androidx.compose.material3.rememberDrawerState
import androidx.compose.runtime.Composable
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
@@ -38,17 +49,99 @@
@OptIn(ExperimentalMaterial3Api::class)
@Sampled
@Composable
-fun NavigationDrawerSample() {
+fun ModalNavigationDrawerSample() {
val drawerState = rememberDrawerState(DrawerValue.Closed)
val scope = rememberCoroutineScope()
- NavigationDrawer(
+ // icons to mimic drawer destinations
+ val items = listOf(Icons.Default.Favorite, Icons.Default.Face, Icons.Default.Email)
+ val selectedItem = remember { mutableStateOf(items[0]) }
+ ModalNavigationDrawer(
drawerState = drawerState,
drawerContent = {
- Button(
- modifier = Modifier.align(Alignment.CenterHorizontally).padding(top = 16.dp),
- onClick = { scope.launch { drawerState.close() } },
- content = { Text("Close Drawer") }
- )
+ items.forEach { item ->
+ NavigationDrawerItem(
+ icon = { Icon(item, contentDescription = null) },
+ label = { Text(item.name) },
+ selected = item == selectedItem.value,
+ onClick = {
+ scope.launch { drawerState.close() }
+ selectedItem.value = item
+ },
+ modifier = Modifier.padding(NavigationDrawerItemDefaults.ItemPadding)
+ )
+ }
+ },
+ content = {
+ Column(
+ modifier = Modifier.fillMaxSize().padding(16.dp),
+ horizontalAlignment = Alignment.CenterHorizontally
+ ) {
+ Text(text = if (drawerState.isClosed) ">>> Swipe >>>" else "<<< Swipe <<<")
+ Spacer(Modifier.height(20.dp))
+ Button(onClick = { scope.launch { drawerState.open() } }) {
+ Text("Click to open")
+ }
+ }
+ }
+ )
+}
+
+@OptIn(ExperimentalMaterial3Api::class)
+@Sampled
+@Composable
+fun PermanentNavigationDrawerSample() {
+ // icons to mimic drawer destinations
+ val items = listOf(Icons.Default.Favorite, Icons.Default.Face, Icons.Default.Email)
+ val selectedItem = remember { mutableStateOf(items[0]) }
+ PermanentNavigationDrawer(
+ drawerContent = {
+ items.forEach { item ->
+ NavigationDrawerItem(
+ icon = { Icon(item, contentDescription = null) },
+ label = { Text(item.name) },
+ selected = item == selectedItem.value,
+ onClick = {
+ selectedItem.value = item
+ },
+ modifier = Modifier.padding(horizontal = 12.dp)
+ )
+ }
+ },
+ content = {
+ Column(
+ modifier = Modifier.fillMaxSize().padding(16.dp),
+ horizontalAlignment = Alignment.CenterHorizontally
+ ) {
+ Text(text = "Application content")
+ }
+ }
+ )
+}
+
+@OptIn(ExperimentalMaterial3Api::class)
+@Sampled
+@Composable
+fun DismissibleNavigationDrawerSample() {
+ val drawerState = rememberDrawerState(DrawerValue.Closed)
+ val scope = rememberCoroutineScope()
+ // icons to mimic drawer destinations
+ val items = listOf(Icons.Default.Favorite, Icons.Default.Face, Icons.Default.Email)
+ val selectedItem = remember { mutableStateOf(items[0]) }
+ DismissibleNavigationDrawer(
+ drawerState = drawerState,
+ drawerContent = {
+ items.forEach { item ->
+ NavigationDrawerItem(
+ icon = { Icon(item, contentDescription = null) },
+ label = { Text(item.name) },
+ selected = item == selectedItem.value,
+ onClick = {
+ scope.launch { drawerState.close() }
+ selectedItem.value = item
+ },
+ modifier = Modifier.padding(horizontal = 12.dp)
+ )
+ }
},
content = {
Column(
diff --git a/compose/material3/material3/src/androidAndroidTest/kotlin/androidx/compose/material3/NavigationDrawerTest.kt b/compose/material3/material3/src/androidAndroidTest/kotlin/androidx/compose/material3/DismissibleNavigationDrawerTest.kt
similarity index 64%
copy from compose/material3/material3/src/androidAndroidTest/kotlin/androidx/compose/material3/NavigationDrawerTest.kt
copy to compose/material3/material3/src/androidAndroidTest/kotlin/androidx/compose/material3/DismissibleNavigationDrawerTest.kt
index ec056ca..377cce0 100644
--- a/compose/material3/material3/src/androidAndroidTest/kotlin/androidx/compose/material3/NavigationDrawerTest.kt
+++ b/compose/material3/material3/src/androidAndroidTest/kotlin/androidx/compose/material3/DismissibleNavigationDrawerTest.kt
@@ -1,5 +1,5 @@
/*
- * Copyright 2021 The Android Open Source Project
+ * Copyright 2022 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.
@@ -31,12 +31,10 @@
import androidx.compose.ui.semantics.SemanticsProperties
import androidx.compose.ui.test.SemanticsMatcher
import androidx.compose.ui.test.assert
-import androidx.compose.ui.test.assertHasClickAction
import androidx.compose.ui.test.assertLeftPositionInRootIsEqualTo
import androidx.compose.ui.test.assertWidthIsEqualTo
import androidx.compose.ui.test.click
import androidx.compose.ui.test.junit4.createComposeRule
-import androidx.compose.ui.test.onNodeWithContentDescription
import androidx.compose.ui.test.onNodeWithTag
import androidx.compose.ui.test.onParent
import androidx.compose.ui.test.performClick
@@ -52,7 +50,6 @@
import androidx.test.filters.SmallTest
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.runBlocking
-import org.junit.Assert.assertEquals
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
@@ -60,22 +57,18 @@
@MediumTest
@RunWith(AndroidJUnit4::class)
@OptIn(ExperimentalMaterial3Api::class)
-class NavigationDrawerTest {
+class DismissibleNavigationDrawerTest {
@get:Rule
val rule = createComposeRule()
- private fun advanceClock() {
- rule.mainClock.advanceTimeBy(100_000L)
- }
-
val NavigationDrawerWidth = NavigationDrawerTokens.ContainerWidth
@Test
- fun navigationDrawer_testOffset_whenOpen() {
+ fun dismissibleNavigationDrawer_testOffset_whenOpen() {
rule.setMaterialContent(lightColorScheme()) {
val drawerState = rememberDrawerState(DrawerValue.Open)
- NavigationDrawer(
+ DismissibleNavigationDrawer(
drawerState = drawerState,
drawerContent = {
Box(Modifier.fillMaxSize().testTag("content"))
@@ -89,10 +82,10 @@
}
@Test
- fun navigationDrawer_testOffset_whenClosed() {
+ fun dismissibleNavigationDrawer_testOffset_whenClosed() {
rule.setMaterialContent(lightColorScheme()) {
val drawerState = rememberDrawerState(DrawerValue.Closed)
- NavigationDrawer(
+ DismissibleNavigationDrawer(
drawerState = drawerState,
drawerContent = {
Box(Modifier.fillMaxSize().testTag("content"))
@@ -106,10 +99,10 @@
}
@Test
- fun navigationDrawer_testWidth_whenOpen() {
+ fun dismissibleNavigationDrawer_testWidth_whenOpen() {
rule.setMaterialContent(lightColorScheme()) {
val drawerState = rememberDrawerState(DrawerValue.Open)
- NavigationDrawer(
+ DismissibleNavigationDrawer(
drawerState = drawerState,
drawerContent = {
Box(Modifier.fillMaxSize().testTag("content"))
@@ -119,15 +112,15 @@
}
rule.onNodeWithTag("content")
- .assertWidthIsEqualTo(NavigationDrawerTokens.ContainerWidth)
+ .assertWidthIsEqualTo(NavigationDrawerWidth)
}
@Test
@SmallTest
- fun navigationDrawer_hasPaneTitle() {
+ fun dismissibleNavigationDrawer_hasPaneTitle() {
lateinit var navigationMenu: String
rule.setMaterialContent(lightColorScheme()) {
- NavigationDrawer(
+ DismissibleNavigationDrawer(
drawerState = rememberDrawerState(DrawerValue.Open),
drawerContent = {
Box(Modifier.fillMaxSize().testTag("navigationDrawerTag"))
@@ -144,11 +137,11 @@
@Test
@LargeTest
- fun navigationDrawer_openAndClose(): Unit = runBlocking(AutoTestFrameClock()) {
+ fun dismissibleNavigationDrawer_openAndClose(): Unit = runBlocking(AutoTestFrameClock()) {
lateinit var drawerState: DrawerState
rule.setMaterialContent(lightColorScheme()) {
drawerState = rememberDrawerState(DrawerValue.Closed)
- NavigationDrawer(
+ DismissibleNavigationDrawer(
drawerState = drawerState,
drawerContent = {
Box(Modifier.fillMaxSize().testTag(DrawerTestTag))
@@ -173,11 +166,11 @@
@Test
@LargeTest
- fun navigationDrawer_animateTo(): Unit = runBlocking(AutoTestFrameClock()) {
+ fun dismissibleNavigationDrawer_animateTo(): Unit = runBlocking(AutoTestFrameClock()) {
lateinit var drawerState: DrawerState
rule.setMaterialContent(lightColorScheme()) {
drawerState = rememberDrawerState(DrawerValue.Closed)
- NavigationDrawer(
+ DismissibleNavigationDrawer(
drawerState = drawerState,
drawerContent = {
Box(Modifier.fillMaxSize().testTag(DrawerTestTag))
@@ -202,11 +195,11 @@
@Test
@LargeTest
- fun navigationDrawer_snapTo(): Unit = runBlocking(AutoTestFrameClock()) {
+ fun dismissibleNavigationDrawer_snapTo(): Unit = runBlocking(AutoTestFrameClock()) {
lateinit var drawerState: DrawerState
rule.setMaterialContent(lightColorScheme()) {
drawerState = rememberDrawerState(DrawerValue.Closed)
- NavigationDrawer(
+ DismissibleNavigationDrawer(
drawerState = drawerState,
drawerContent = {
Box(Modifier.fillMaxSize().testTag(DrawerTestTag))
@@ -231,11 +224,11 @@
@Test
@LargeTest
- fun navigationDrawer_currentValue(): Unit = runBlocking(AutoTestFrameClock()) {
+ fun dismissibleNavigationDrawer_currentValue(): Unit = runBlocking(AutoTestFrameClock()) {
lateinit var drawerState: DrawerState
rule.setMaterialContent(lightColorScheme()) {
drawerState = rememberDrawerState(DrawerValue.Closed)
- NavigationDrawer(
+ DismissibleNavigationDrawer(
drawerState = drawerState,
drawerContent = {
Box(Modifier.fillMaxSize().testTag(DrawerTestTag))
@@ -260,90 +253,51 @@
@Test
@LargeTest
- fun navigationDrawer_bodyContent_clickable(): Unit = runBlocking(AutoTestFrameClock()) {
- var drawerClicks = 0
- var bodyClicks = 0
- lateinit var drawerState: DrawerState
- rule.setMaterialContent(lightColorScheme()) {
- drawerState = rememberDrawerState(DrawerValue.Closed)
- // emulate click on the screen
- NavigationDrawer(
- drawerState = drawerState,
- drawerContent = {
- Box(Modifier.fillMaxSize().clickable { drawerClicks += 1 })
- },
- content = {
- Box(Modifier.testTag(DrawerTestTag).fillMaxSize().clickable { bodyClicks += 1 })
- }
- )
- }
+ fun dismissibleNavigationDrawer_drawerContent_doesntPropagateClicksWhenOpen(): Unit =
+ runBlocking(
+ AutoTestFrameClock()
+ ) {
+ var bodyClicks = 0
+ lateinit var drawerState: DrawerState
+ rule.setMaterialContent(lightColorScheme()) {
+ drawerState = rememberDrawerState(DrawerValue.Closed)
+ DismissibleNavigationDrawer(
+ drawerState = drawerState,
+ drawerContent = {
+ Box(Modifier.fillMaxSize().testTag(DrawerTestTag))
+ },
+ content = {
+ Box(Modifier.fillMaxSize().clickable { bodyClicks += 1 })
+ }
+ )
+ }
- // Click in the middle of the drawer (which is the middle of the body)
- rule.onNodeWithTag(DrawerTestTag).performTouchInput { click() }
+ // Click in the middle of the drawer
+ rule.onNodeWithTag(DrawerTestTag).performClick()
- rule.runOnIdle {
- assertThat(drawerClicks).isEqualTo(0)
- assertThat(bodyClicks).isEqualTo(1)
- }
- drawerState.open()
+ rule.runOnIdle {
+ assertThat(bodyClicks).isEqualTo(1)
+ }
+ drawerState.open()
- // Click on the left-center pixel of the drawer
- rule.onNodeWithTag(DrawerTestTag).performTouchInput {
- click(centerLeft)
- }
+ // Click on the left-center pixel of the drawer
+ rule.onNodeWithTag(DrawerTestTag).performTouchInput {
+ click(centerLeft)
+ }
- rule.runOnIdle {
- assertThat(drawerClicks).isEqualTo(1)
- assertThat(bodyClicks).isEqualTo(1)
+ rule.runOnIdle {
+ assertThat(bodyClicks).isEqualTo(1)
+ }
}
- }
@Test
@LargeTest
- fun navigationDrawer_drawerContent_doesntPropagateClicksWhenOpen(): Unit = runBlocking(
- AutoTestFrameClock()
- ) {
- var bodyClicks = 0
- lateinit var drawerState: DrawerState
- rule.setMaterialContent(lightColorScheme()) {
- drawerState = rememberDrawerState(DrawerValue.Closed)
- NavigationDrawer(
- drawerState = drawerState,
- drawerContent = {
- Box(Modifier.fillMaxSize().testTag(DrawerTestTag))
- },
- content = {
- Box(Modifier.fillMaxSize().clickable { bodyClicks += 1 })
- }
- )
- }
-
- // Click in the middle of the drawer
- rule.onNodeWithTag(DrawerTestTag).performClick()
-
- rule.runOnIdle {
- assertThat(bodyClicks).isEqualTo(1)
- }
- drawerState.open()
-
- // Click on the left-center pixel of the drawer
- rule.onNodeWithTag(DrawerTestTag).performTouchInput {
- click(centerLeft)
- }
-
- rule.runOnIdle {
- assertThat(bodyClicks).isEqualTo(1)
- }
- }
-
- @Test
- @LargeTest
- fun navigationDrawer_openBySwipe() {
+ fun dismissibleNavigationDrawer_openBySwipe() {
lateinit var drawerState: DrawerState
rule.setMaterialContent(lightColorScheme()) {
drawerState = rememberDrawerState(DrawerValue.Closed)
Box(Modifier.testTag(DrawerTestTag)) {
- NavigationDrawer(
+ DismissibleNavigationDrawer(
drawerState = drawerState,
drawerContent = {
Box(Modifier.fillMaxSize().background(color = Color.Magenta))
@@ -372,7 +326,7 @@
@Test
@LargeTest
- fun navigationDrawer_confirmStateChangeRespect() {
+ fun dismissibleNavigationDrawer_confirmStateChangeRespect() {
lateinit var drawerState: DrawerState
rule.setMaterialContent(lightColorScheme()) {
drawerState = rememberDrawerState(
@@ -382,7 +336,7 @@
}
)
Box(Modifier.testTag(DrawerTestTag)) {
- NavigationDrawer(
+ DismissibleNavigationDrawer(
drawerState = drawerState,
drawerContent = {
Box(
@@ -417,14 +371,14 @@
@Test
@LargeTest
- fun navigationDrawer_openBySwipe_rtl() {
+ fun dismissibleNavigationDrawer_openBySwipe_rtl() {
lateinit var drawerState: DrawerState
rule.setMaterialContent(lightColorScheme()) {
drawerState = rememberDrawerState(DrawerValue.Closed)
// emulate click on the screen
CompositionLocalProvider(LocalLayoutDirection provides LayoutDirection.Rtl) {
Box(Modifier.testTag(DrawerTestTag)) {
- NavigationDrawer(
+ DismissibleNavigationDrawer(
drawerState = drawerState,
drawerContent = {
Box(Modifier.fillMaxSize().background(color = Color.Magenta))
@@ -454,76 +408,43 @@
@Test
@LargeTest
- fun navigationDrawer_noDismissActionWhenClosed_hasDissmissActionWhenOpen(): Unit = runBlocking(
- AutoTestFrameClock()
- ) {
- lateinit var drawerState: DrawerState
- rule.setMaterialContent(lightColorScheme()) {
- drawerState = rememberDrawerState(DrawerValue.Closed)
- NavigationDrawer(
- drawerState = drawerState,
- drawerContent = {
- Box(Modifier.fillMaxSize().testTag(DrawerTestTag))
- },
- content = {}
- )
+ fun dismissibleNavigationDrawer_noDismissActionWhenClosed_hasDissmissActionWhenOpen(): Unit =
+ runBlocking(
+ AutoTestFrameClock()
+ ) {
+ lateinit var drawerState: DrawerState
+ rule.setMaterialContent(lightColorScheme()) {
+ drawerState = rememberDrawerState(DrawerValue.Closed)
+ DismissibleNavigationDrawer(
+ drawerState = drawerState,
+ drawerContent = {
+ Box(Modifier.fillMaxSize().testTag(DrawerTestTag))
+ },
+ content = {}
+ )
+ }
+
+ // Drawer should start in closed state and have no dismiss action
+ rule.onNodeWithTag(DrawerTestTag, useUnmergedTree = true)
+ .onParent()
+ .assert(SemanticsMatcher.keyNotDefined(SemanticsActions.Dismiss))
+
+ // When the drawer state is set to Opened
+ drawerState.open()
+ // Then the drawer should be opened and have dismiss action
+ rule.onNodeWithTag(DrawerTestTag, useUnmergedTree = true)
+ .onParent()
+ .assert(SemanticsMatcher.keyIsDefined(SemanticsActions.Dismiss))
+
+ // When the drawer state is set to Closed using dismiss action
+ rule.onNodeWithTag(DrawerTestTag, useUnmergedTree = true)
+ .onParent()
+ .performSemanticsAction(SemanticsActions.Dismiss)
+ // Then the drawer should be closed and have no dismiss action
+ rule.onNodeWithTag(DrawerTestTag, useUnmergedTree = true)
+ .onParent()
+ .assert(SemanticsMatcher.keyNotDefined(SemanticsActions.Dismiss))
}
-
- // Drawer should start in closed state and have no dismiss action
- rule.onNodeWithTag(DrawerTestTag, useUnmergedTree = true)
- .onParent()
- .assert(SemanticsMatcher.keyNotDefined(SemanticsActions.Dismiss))
-
- // When the drawer state is set to Opened
- drawerState.open()
- // Then the drawer should be opened and have dismiss action
- rule.onNodeWithTag(DrawerTestTag, useUnmergedTree = true)
- .onParent()
- .assert(SemanticsMatcher.keyIsDefined(SemanticsActions.Dismiss))
-
- // When the drawer state is set to Closed using dismiss action
- rule.onNodeWithTag(DrawerTestTag, useUnmergedTree = true)
- .onParent()
- .performSemanticsAction(SemanticsActions.Dismiss)
- // Then the drawer should be closed and have no dismiss action
- rule.onNodeWithTag(DrawerTestTag, useUnmergedTree = true)
- .onParent()
- .assert(SemanticsMatcher.keyNotDefined(SemanticsActions.Dismiss))
- }
-
- @Test
- fun navigationDrawer_scrimNode_reportToSemanticsWhenOpen_notReportToSemanticsWhenClosed() {
- val topTag = "navigationDrawer"
- lateinit var closeDrawer: String
- rule.setMaterialContent(lightColorScheme()) {
- NavigationDrawer(
- modifier = Modifier.testTag(topTag),
- drawerState = rememberDrawerState(DrawerValue.Open),
- drawerContent = {
- Box(Modifier.fillMaxSize().testTag(DrawerTestTag))
- },
- content = {
- Box(Modifier.fillMaxSize().testTag("body"))
- }
- )
- closeDrawer = getString(Strings.CloseDrawer)
- }
-
- // The drawer should be opened
- rule.onNodeWithTag(DrawerTestTag).assertLeftPositionInRootIsEqualTo(0.dp)
-
- var topNode = rule.onNodeWithTag(topTag).fetchSemanticsNode()
- assertEquals(3, topNode.children.size)
- rule.onNodeWithContentDescription(closeDrawer)
- .assertHasClickAction()
- .performSemanticsAction(SemanticsActions.OnClick)
-
- // Then the drawer should be closed
- rule.onNodeWithTag(DrawerTestTag).assertLeftPositionInRootIsEqualTo(-NavigationDrawerWidth)
-
- topNode = rule.onNodeWithTag(topTag).fetchSemanticsNode()
- assertEquals(2, topNode.children.size)
- }
}
private val DrawerTestTag = "drawer"
\ No newline at end of file
diff --git a/compose/material3/material3/src/androidAndroidTest/kotlin/androidx/compose/material3/NavigationDrawerScreenshotTest.kt b/compose/material3/material3/src/androidAndroidTest/kotlin/androidx/compose/material3/ModalNavigationDrawerScreenshotTest.kt
similarity index 96%
rename from compose/material3/material3/src/androidAndroidTest/kotlin/androidx/compose/material3/NavigationDrawerScreenshotTest.kt
rename to compose/material3/material3/src/androidAndroidTest/kotlin/androidx/compose/material3/ModalNavigationDrawerScreenshotTest.kt
index d4f5273..175d8e5 100644
--- a/compose/material3/material3/src/androidAndroidTest/kotlin/androidx/compose/material3/NavigationDrawerScreenshotTest.kt
+++ b/compose/material3/material3/src/androidAndroidTest/kotlin/androidx/compose/material3/ModalNavigationDrawerScreenshotTest.kt
@@ -41,7 +41,7 @@
@RunWith(AndroidJUnit4::class)
@OptIn(ExperimentalMaterial3Api::class)
@SdkSuppress(minSdkVersion = Build.VERSION_CODES.O)
-class NavigationDrawerScreenshotTest {
+class ModalNavigationDrawerScreenshotTest {
@Suppress("DEPRECATION")
@get:Rule
@@ -53,7 +53,7 @@
private fun ComposeContentTestRule.setnavigationDrawer(drawerValue: DrawerValue) {
setMaterialContent(lightColorScheme()) {
Box(Modifier.requiredSize(400.dp, 32.dp).testTag(ContainerTestTag)) {
- NavigationDrawer(
+ ModalNavigationDrawer(
drawerState = rememberDrawerState(drawerValue),
drawerContent = {},
content = {
@@ -70,7 +70,7 @@
setMaterialContent(darkColorScheme()) {
Surface {
Box(Modifier.requiredSize(400.dp, 32.dp).testTag(ContainerTestTag)) {
- NavigationDrawer(
+ ModalNavigationDrawer(
drawerState = rememberDrawerState(drawerValue),
drawerContent = {},
content = {
diff --git a/compose/material3/material3/src/androidAndroidTest/kotlin/androidx/compose/material3/NavigationDrawerTest.kt b/compose/material3/material3/src/androidAndroidTest/kotlin/androidx/compose/material3/ModalNavigationDrawerTest.kt
similarity index 96%
rename from compose/material3/material3/src/androidAndroidTest/kotlin/androidx/compose/material3/NavigationDrawerTest.kt
rename to compose/material3/material3/src/androidAndroidTest/kotlin/androidx/compose/material3/ModalNavigationDrawerTest.kt
index ec056ca..03fefeb 100644
--- a/compose/material3/material3/src/androidAndroidTest/kotlin/androidx/compose/material3/NavigationDrawerTest.kt
+++ b/compose/material3/material3/src/androidAndroidTest/kotlin/androidx/compose/material3/ModalNavigationDrawerTest.kt
@@ -60,7 +60,7 @@
@MediumTest
@RunWith(AndroidJUnit4::class)
@OptIn(ExperimentalMaterial3Api::class)
-class NavigationDrawerTest {
+class ModalNavigationDrawerTest {
@get:Rule
val rule = createComposeRule()
@@ -75,7 +75,7 @@
fun navigationDrawer_testOffset_whenOpen() {
rule.setMaterialContent(lightColorScheme()) {
val drawerState = rememberDrawerState(DrawerValue.Open)
- NavigationDrawer(
+ ModalNavigationDrawer(
drawerState = drawerState,
drawerContent = {
Box(Modifier.fillMaxSize().testTag("content"))
@@ -92,7 +92,7 @@
fun navigationDrawer_testOffset_whenClosed() {
rule.setMaterialContent(lightColorScheme()) {
val drawerState = rememberDrawerState(DrawerValue.Closed)
- NavigationDrawer(
+ ModalNavigationDrawer(
drawerState = drawerState,
drawerContent = {
Box(Modifier.fillMaxSize().testTag("content"))
@@ -109,7 +109,7 @@
fun navigationDrawer_testWidth_whenOpen() {
rule.setMaterialContent(lightColorScheme()) {
val drawerState = rememberDrawerState(DrawerValue.Open)
- NavigationDrawer(
+ ModalNavigationDrawer(
drawerState = drawerState,
drawerContent = {
Box(Modifier.fillMaxSize().testTag("content"))
@@ -127,7 +127,7 @@
fun navigationDrawer_hasPaneTitle() {
lateinit var navigationMenu: String
rule.setMaterialContent(lightColorScheme()) {
- NavigationDrawer(
+ ModalNavigationDrawer(
drawerState = rememberDrawerState(DrawerValue.Open),
drawerContent = {
Box(Modifier.fillMaxSize().testTag("navigationDrawerTag"))
@@ -148,7 +148,7 @@
lateinit var drawerState: DrawerState
rule.setMaterialContent(lightColorScheme()) {
drawerState = rememberDrawerState(DrawerValue.Closed)
- NavigationDrawer(
+ ModalNavigationDrawer(
drawerState = drawerState,
drawerContent = {
Box(Modifier.fillMaxSize().testTag(DrawerTestTag))
@@ -177,7 +177,7 @@
lateinit var drawerState: DrawerState
rule.setMaterialContent(lightColorScheme()) {
drawerState = rememberDrawerState(DrawerValue.Closed)
- NavigationDrawer(
+ ModalNavigationDrawer(
drawerState = drawerState,
drawerContent = {
Box(Modifier.fillMaxSize().testTag(DrawerTestTag))
@@ -206,7 +206,7 @@
lateinit var drawerState: DrawerState
rule.setMaterialContent(lightColorScheme()) {
drawerState = rememberDrawerState(DrawerValue.Closed)
- NavigationDrawer(
+ ModalNavigationDrawer(
drawerState = drawerState,
drawerContent = {
Box(Modifier.fillMaxSize().testTag(DrawerTestTag))
@@ -235,7 +235,7 @@
lateinit var drawerState: DrawerState
rule.setMaterialContent(lightColorScheme()) {
drawerState = rememberDrawerState(DrawerValue.Closed)
- NavigationDrawer(
+ ModalNavigationDrawer(
drawerState = drawerState,
drawerContent = {
Box(Modifier.fillMaxSize().testTag(DrawerTestTag))
@@ -267,7 +267,7 @@
rule.setMaterialContent(lightColorScheme()) {
drawerState = rememberDrawerState(DrawerValue.Closed)
// emulate click on the screen
- NavigationDrawer(
+ ModalNavigationDrawer(
drawerState = drawerState,
drawerContent = {
Box(Modifier.fillMaxSize().clickable { drawerClicks += 1 })
@@ -307,7 +307,7 @@
lateinit var drawerState: DrawerState
rule.setMaterialContent(lightColorScheme()) {
drawerState = rememberDrawerState(DrawerValue.Closed)
- NavigationDrawer(
+ ModalNavigationDrawer(
drawerState = drawerState,
drawerContent = {
Box(Modifier.fillMaxSize().testTag(DrawerTestTag))
@@ -343,7 +343,7 @@
rule.setMaterialContent(lightColorScheme()) {
drawerState = rememberDrawerState(DrawerValue.Closed)
Box(Modifier.testTag(DrawerTestTag)) {
- NavigationDrawer(
+ ModalNavigationDrawer(
drawerState = drawerState,
drawerContent = {
Box(Modifier.fillMaxSize().background(color = Color.Magenta))
@@ -382,7 +382,7 @@
}
)
Box(Modifier.testTag(DrawerTestTag)) {
- NavigationDrawer(
+ ModalNavigationDrawer(
drawerState = drawerState,
drawerContent = {
Box(
@@ -424,7 +424,7 @@
// emulate click on the screen
CompositionLocalProvider(LocalLayoutDirection provides LayoutDirection.Rtl) {
Box(Modifier.testTag(DrawerTestTag)) {
- NavigationDrawer(
+ ModalNavigationDrawer(
drawerState = drawerState,
drawerContent = {
Box(Modifier.fillMaxSize().background(color = Color.Magenta))
@@ -460,7 +460,7 @@
lateinit var drawerState: DrawerState
rule.setMaterialContent(lightColorScheme()) {
drawerState = rememberDrawerState(DrawerValue.Closed)
- NavigationDrawer(
+ ModalNavigationDrawer(
drawerState = drawerState,
drawerContent = {
Box(Modifier.fillMaxSize().testTag(DrawerTestTag))
@@ -496,7 +496,7 @@
val topTag = "navigationDrawer"
lateinit var closeDrawer: String
rule.setMaterialContent(lightColorScheme()) {
- NavigationDrawer(
+ ModalNavigationDrawer(
modifier = Modifier.testTag(topTag),
drawerState = rememberDrawerState(DrawerValue.Open),
drawerContent = {
diff --git a/compose/material3/material3/src/androidAndroidTest/kotlin/androidx/compose/material3/NavigationDrawerItemScreenshotTest.kt b/compose/material3/material3/src/androidAndroidTest/kotlin/androidx/compose/material3/NavigationDrawerItemScreenshotTest.kt
new file mode 100644
index 0000000..c1c847e
--- /dev/null
+++ b/compose/material3/material3/src/androidAndroidTest/kotlin/androidx/compose/material3/NavigationDrawerItemScreenshotTest.kt
@@ -0,0 +1,210 @@
+/*
+ * Copyright 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.compose.material3
+
+import android.os.Build
+import androidx.compose.foundation.interaction.Interaction
+import androidx.compose.foundation.interaction.MutableInteractionSource
+import androidx.compose.foundation.interaction.PressInteraction
+import androidx.compose.foundation.layout.Column
+import androidx.compose.material.icons.Icons
+import androidx.compose.material.icons.filled.Face
+import androidx.compose.material.icons.filled.Favorite
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.rememberCoroutineScope
+import androidx.compose.testutils.assertAgainstGolden
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.geometry.Offset
+import androidx.compose.ui.platform.testTag
+import androidx.compose.ui.semantics.semantics
+import androidx.compose.ui.test.ExperimentalTestApi
+import androidx.compose.ui.test.captureToImage
+import androidx.compose.ui.test.junit4.createComposeRule
+import androidx.compose.ui.test.onNodeWithTag
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.LargeTest
+import androidx.test.filters.SdkSuppress
+import androidx.test.screenshot.AndroidXScreenshotTestRule
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.launch
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@LargeTest
+@RunWith(AndroidJUnit4::class)
+@SdkSuppress(minSdkVersion = Build.VERSION_CODES.O)
+@OptIn(ExperimentalMaterial3Api::class, ExperimentalTestApi::class)
+class NavigationDrawerItemScreenshotTest {
+ @get:Rule
+ val composeTestRule = createComposeRule()
+
+ @get:Rule
+ val screenshotRule = AndroidXScreenshotTestRule(GOLDEN_MATERIAL3)
+
+ @Test
+ fun lightTheme_defaultColors() {
+ val interactionSource = MutableInteractionSource()
+
+ var scope: CoroutineScope? = null
+
+ composeTestRule.setMaterialContent(lightColorScheme()) {
+ scope = rememberCoroutineScope()
+ DefaultDrawerItems(interactionSource)
+ }
+
+ assertDrawerItemMatches(
+ scope = scope!!,
+ interactionSource = interactionSource,
+ interaction = null,
+ goldenIdentifier = "drawerItem_lightTheme_defaultColors"
+ )
+ }
+
+ @Test
+ fun lightTheme_defaultColors_pressed() {
+ val interactionSource = MutableInteractionSource()
+
+ var scope: CoroutineScope? = null
+
+ composeTestRule.setMaterialContent(lightColorScheme()) {
+ scope = rememberCoroutineScope()
+ DefaultDrawerItems(interactionSource)
+ }
+
+ assertDrawerItemMatches(
+ scope = scope!!,
+ interactionSource = interactionSource,
+ interaction = PressInteraction.Press(Offset(10f, 10f)),
+ goldenIdentifier = "drawerItem_lightTheme_defaultColors_pressed"
+ )
+ }
+
+ @Test
+ fun darkTheme_defaultColors() {
+ val interactionSource = MutableInteractionSource()
+
+ var scope: CoroutineScope? = null
+
+ composeTestRule.setMaterialContent(darkColorScheme()) {
+ scope = rememberCoroutineScope()
+ DefaultDrawerItems(interactionSource)
+ }
+
+ assertDrawerItemMatches(
+ scope = scope!!,
+ interactionSource = interactionSource,
+ interaction = null,
+ goldenIdentifier = "drawerItem_darkTheme_defaultColors"
+ )
+ }
+
+ @Test
+ fun darkTheme_defaultColors_pressed() {
+ val interactionSource = MutableInteractionSource()
+
+ var scope: CoroutineScope? = null
+
+ composeTestRule.setMaterialContent(darkColorScheme()) {
+ scope = rememberCoroutineScope()
+ DefaultDrawerItems(interactionSource)
+ }
+
+ assertDrawerItemMatches(
+ scope = scope!!,
+ interactionSource = interactionSource,
+ interaction = PressInteraction.Press(Offset(10f, 10f)),
+ goldenIdentifier = "drawerItem_darkTheme_defaultColors_pressed"
+ )
+ }
+
+ /**
+ * Asserts that the NavigationDrawerItem matches the screenshot with identifier
+ * [goldenIdentifier].
+ *
+ * @param scope [CoroutineScope] used to interact with [MutableInteractionSource]
+ * @param interactionSource the [MutableInteractionSource] used for the first
+ * [NavigationDrawerItem]
+ * @param interaction the [Interaction] to assert for, or `null` if no [Interaction].
+ * @param goldenIdentifier the identifier for the corresponding screenshot
+ */
+ private fun assertDrawerItemMatches(
+ scope: CoroutineScope,
+ interactionSource: MutableInteractionSource,
+ interaction: Interaction? = null,
+ goldenIdentifier: String
+ ) {
+ if (interaction != null) {
+ composeTestRule.runOnIdle {
+ // Start ripple
+ scope.launch {
+ interactionSource.emit(interaction)
+ }
+ }
+
+ composeTestRule.waitForIdle()
+ // Ripples are drawn on the RenderThread, not the main (UI) thread, so we can't
+ // properly wait for synchronization. Instead just wait until after the ripples are
+ // finished animating.
+ Thread.sleep(300)
+ }
+
+ // Capture and compare screenshots
+ composeTestRule.onNodeWithTag(Tag)
+ .captureToImage()
+ .assertAgainstGolden(screenshotRule, goldenIdentifier)
+ }
+}
+
+@OptIn(ExperimentalMaterial3Api::class)
+@Composable
+private fun DefaultDrawerItems(
+ interactionSource: MutableInteractionSource,
+) {
+ Column(modifier = Modifier.semantics(mergeDescendants = true) {}.testTag(Tag)) {
+ NavigationDrawerItem(
+ icon = { Icon(Icons.Filled.Favorite, null) },
+ label = { Text("Favorites") },
+ selected = true,
+ onClick = {},
+ interactionSource = interactionSource
+ )
+ NavigationDrawerItem(
+ label = { Text("Favorites") },
+ selected = true,
+ onClick = {},
+ interactionSource = interactionSource
+ )
+ NavigationDrawerItem(
+ icon = { Icon(Icons.Filled.Face, null) },
+ label = { Text("Face") },
+ selected = false,
+ onClick = {},
+ interactionSource = interactionSource
+ )
+ NavigationDrawerItem(
+ icon = { Icon(Icons.Filled.Face, null) },
+ label = { Text("Face") },
+ badge = { Text("100+") },
+ selected = false,
+ onClick = {},
+ interactionSource = interactionSource
+ )
+ }
+}
+
+private const val Tag = "NavigationDrawerItem"
\ No newline at end of file
diff --git a/compose/material3/material3/src/androidAndroidTest/kotlin/androidx/compose/material3/NavigationDrawerItemTest.kt b/compose/material3/material3/src/androidAndroidTest/kotlin/androidx/compose/material3/NavigationDrawerItemTest.kt
new file mode 100644
index 0000000..f2c4bda
--- /dev/null
+++ b/compose/material3/material3/src/androidAndroidTest/kotlin/androidx/compose/material3/NavigationDrawerItemTest.kt
@@ -0,0 +1,182 @@
+/*
+ * Copyright 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.compose.material3
+
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.width
+import androidx.compose.material.icons.Icons
+import androidx.compose.material.icons.filled.Face
+import androidx.compose.material.icons.filled.Favorite
+import androidx.compose.material3.tokens.NavigationDrawerTokens
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.platform.testTag
+import androidx.compose.ui.semantics.Role
+import androidx.compose.ui.semantics.SemanticsProperties
+import androidx.compose.ui.test.SemanticsMatcher
+import androidx.compose.ui.test.assert
+import androidx.compose.ui.test.assertHasClickAction
+import androidx.compose.ui.test.assertHeightIsAtLeast
+import androidx.compose.ui.test.assertIsNotSelected
+import androidx.compose.ui.test.assertIsSelected
+import androidx.compose.ui.test.assertLeftPositionInRootIsEqualTo
+import androidx.compose.ui.test.assertWidthIsEqualTo
+import androidx.compose.ui.test.getBoundsInRoot
+import androidx.compose.ui.test.junit4.createComposeRule
+import androidx.compose.ui.test.onNodeWithTag
+import androidx.compose.ui.test.onNodeWithText
+import androidx.compose.ui.test.onRoot
+import androidx.compose.ui.unit.dp
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.MediumTest
+import com.google.common.truth.Truth.assertThat
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@MediumTest
+@RunWith(AndroidJUnit4::class)
+@OptIn(ExperimentalMaterial3Api::class)
+class NavigationDrawerItemTest {
+
+ @get:Rule
+ val rule = createComposeRule()
+
+ val DrawerItemTag = "drawer_item_tag"
+
+ @Test
+ fun navigationDrawerItem_sizes() {
+ rule.setMaterialContent(lightColorScheme()) {
+ Column(Modifier.width(264.dp)) {
+ NavigationDrawerItem(
+ icon = {},
+ label = {},
+ selected = true,
+ onClick = {},
+ modifier = Modifier.testTag(DrawerItemTag)
+ )
+ }
+ }
+
+ rule.onNodeWithTag(DrawerItemTag)
+ .assertWidthIsEqualTo(264.dp)
+ .assertHeightIsAtLeast(NavigationDrawerTokens.ActiveIndicatorHeight)
+ }
+
+ @Test
+ fun navigationDrawerItem_paddings() {
+ rule.setMaterialContent(lightColorScheme()) {
+ Column(Modifier.width(264.dp)) {
+ NavigationDrawerItem(
+ icon = {
+ Icon(
+ Icons.Default.Face,
+ contentDescription = null,
+ modifier = Modifier.testTag("icon")
+ )
+ },
+ label = {
+ Text("Label")
+ },
+ badge = {
+ Text("Badge")
+ },
+ selected = true,
+ onClick = {},
+ modifier = Modifier.testTag(DrawerItemTag)
+ )
+ }
+ }
+
+ rule.onNodeWithTag("icon", useUnmergedTree = true)
+ // should be 16dp padding from the start
+ .assertLeftPositionInRootIsEqualTo(16.dp)
+
+ val iconLabelPadding =
+ rule.onNodeWithText("Label", useUnmergedTree = true).getBoundsInRoot().left -
+ rule.onNodeWithTag("icon", useUnmergedTree = true).getBoundsInRoot().right
+ // should be 12dp padding between an icon and a label, also rounding error
+ assertThat(iconLabelPadding - 12.dp).isLessThan(0.5.dp)
+
+ val badgePadding =
+ rule.onRoot().getBoundsInRoot().right -
+ rule.onNodeWithText("Badge", useUnmergedTree = true).getBoundsInRoot().right
+ // 24 at the end
+ assertThat(badgePadding).isEqualTo(24.dp)
+ }
+
+ @Test
+ fun navigationDrawerItem_labelOnly_paddings() {
+ rule.setMaterialContent(lightColorScheme()) {
+ Column(Modifier.width(264.dp)) {
+ NavigationDrawerItem(
+ label = {
+ Text("Label")
+ },
+ selected = true,
+ onClick = {},
+ modifier = Modifier.testTag(DrawerItemTag)
+ )
+ }
+ }
+
+ rule.onNodeWithText("Label", useUnmergedTree = true)
+ // should be 16dp padding from the start
+ .assertLeftPositionInRootIsEqualTo(16.dp)
+ }
+
+ @Test
+ fun defaultSemantics() {
+ rule.setMaterialContent(lightColorScheme()) {
+ PermanentNavigationDrawer(
+ drawerContent = {
+ NavigationDrawerItem(
+ modifier = Modifier.testTag("selected_item"),
+ icon = {
+ Icon(Icons.Filled.Favorite, null)
+ },
+ label = {
+ Text("ItemText")
+ },
+ selected = true,
+ onClick = {}
+ )
+ NavigationDrawerItem(
+ modifier = Modifier.testTag("unselected_item"),
+ icon = {
+ Icon(Icons.Filled.Favorite, null)
+ },
+ label = {
+ Text("ItemText")
+ },
+ selected = false,
+ onClick = {}
+ )
+ }
+ ) {
+ }
+ }
+
+ rule.onNodeWithTag("selected_item")
+ .assert(SemanticsMatcher.expectValue(SemanticsProperties.Role, Role.Tab))
+ .assertIsSelected()
+ .assertHasClickAction()
+ rule.onNodeWithTag("unselected_item")
+ .assert(SemanticsMatcher.expectValue(SemanticsProperties.Role, Role.Tab))
+ .assertIsNotSelected()
+ .assertHasClickAction()
+ }
+}
\ No newline at end of file
diff --git a/compose/material3/material3/src/androidAndroidTest/kotlin/androidx/compose/material3/PermanentNavigationDrawerTest.kt b/compose/material3/material3/src/androidAndroidTest/kotlin/androidx/compose/material3/PermanentNavigationDrawerTest.kt
new file mode 100644
index 0000000..ca9a847
--- /dev/null
+++ b/compose/material3/material3/src/androidAndroidTest/kotlin/androidx/compose/material3/PermanentNavigationDrawerTest.kt
@@ -0,0 +1,120 @@
+/*
+ * Copyright 2022 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.material3
+
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.material3.tokens.NavigationDrawerTokens
+import androidx.compose.runtime.CompositionLocalProvider
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.platform.LocalLayoutDirection
+import androidx.compose.ui.platform.testTag
+import androidx.compose.ui.semantics.SemanticsProperties
+import androidx.compose.ui.test.SemanticsMatcher
+import androidx.compose.ui.test.assert
+import androidx.compose.ui.test.assertLeftPositionInRootIsEqualTo
+import androidx.compose.ui.test.assertWidthIsEqualTo
+import androidx.compose.ui.test.junit4.createComposeRule
+import androidx.compose.ui.test.onNodeWithTag
+import androidx.compose.ui.test.onParent
+import androidx.compose.ui.unit.LayoutDirection
+import androidx.compose.ui.unit.dp
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.MediumTest
+import androidx.test.filters.SmallTest
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@MediumTest
+@RunWith(AndroidJUnit4::class)
+@OptIn(ExperimentalMaterial3Api::class)
+class PermanentNavigationDrawerTest {
+
+ @get:Rule
+ val rule = createComposeRule()
+
+ @Test
+ fun permanentNavigationDrawer_testOffset() {
+ rule.setMaterialContent(lightColorScheme()) {
+ PermanentNavigationDrawer(
+ drawerContent = {
+ Box(Modifier.fillMaxSize().testTag("content"))
+ },
+ content = {}
+ )
+ }
+
+ rule.onNodeWithTag("content")
+ .assertLeftPositionInRootIsEqualTo(0.dp)
+ }
+
+ @Test
+ fun permanentNavigationDrawer_testOffset_rtl() {
+ rule.setMaterialContent(lightColorScheme()) {
+ CompositionLocalProvider(LocalLayoutDirection provides LayoutDirection.Rtl) {
+ PermanentNavigationDrawer(
+ drawerContent = {
+ Box(Modifier.fillMaxSize().testTag("content"))
+ },
+ content = {}
+ )
+ }
+ }
+
+ rule.onNodeWithTag("content")
+ .assertLeftPositionInRootIsEqualTo(
+ rule.rootWidth() - NavigationDrawerTokens.ContainerWidth
+ )
+ }
+
+ @Test
+ fun permanentNavigationDrawer_testWidth() {
+ rule.setMaterialContent(lightColorScheme()) {
+ PermanentNavigationDrawer(
+ drawerContent = {
+ Box(Modifier.fillMaxSize().testTag("content"))
+ },
+ content = {}
+ )
+ }
+
+ rule.onNodeWithTag("content")
+ .assertWidthIsEqualTo(NavigationDrawerTokens.ContainerWidth)
+ }
+
+ @Test
+ @SmallTest
+ fun permanentNavigationDrawer_hasPaneTitle() {
+ lateinit var navigationMenu: String
+ rule.setMaterialContent(lightColorScheme()) {
+ PermanentNavigationDrawer(
+ drawerContent = {
+ Box(Modifier.fillMaxSize().testTag("navigationDrawerTag"))
+ },
+ content = {}
+ )
+ navigationMenu = getString(Strings.NavigationMenu)
+ }
+
+ rule.onNodeWithTag("navigationDrawerTag", useUnmergedTree = true)
+ .onParent()
+ .assert(SemanticsMatcher.expectValue(SemanticsProperties.PaneTitle, navigationMenu))
+ }
+}
+
+private val DrawerTestTag = "drawer"
\ No newline at end of file
diff --git a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/NavigationDrawer.kt b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/NavigationDrawer.kt
index 44707f5..82741fb 100644
--- a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/NavigationDrawer.kt
+++ b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/NavigationDrawer.kt
@@ -21,28 +21,44 @@
import androidx.compose.foundation.Canvas
import androidx.compose.foundation.gestures.Orientation
import androidx.compose.foundation.gestures.detectTapGestures
+import androidx.compose.foundation.interaction.Interaction
+import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.ColumnScope
+import androidx.compose.foundation.layout.PaddingValues
+import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.offset
+import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.sizeIn
+import androidx.compose.foundation.layout.width
+import androidx.compose.foundation.selection.selectable
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material3.tokens.NavigationDrawerTokens
import androidx.compose.material3.tokens.PaletteTokens
import androidx.compose.runtime.Composable
+import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.runtime.Stable
import androidx.compose.runtime.State
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
+import androidx.compose.runtime.rememberUpdatedState
import androidx.compose.runtime.saveable.Saver
import androidx.compose.runtime.saveable.rememberSaveable
+import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.graphics.RectangleShape
import androidx.compose.ui.graphics.Shape
import androidx.compose.ui.input.pointer.pointerInput
+import androidx.compose.ui.layout.Layout
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.platform.LocalLayoutDirection
+import androidx.compose.ui.semantics.Role
import androidx.compose.ui.semantics.contentDescription
import androidx.compose.ui.semantics.dismiss
import androidx.compose.ui.semantics.onClick
@@ -73,7 +89,7 @@
}
/**
- * State of the [NavigationDrawer] composable.
+ * State of the [ModalNavigationDrawer] and [DismissibleNavigationDrawer] composable.
*
* @param initialValue The initial value of the state.
* @param confirmStateChange Optional callback invoked to confirm or veto a pending state change.
@@ -177,7 +193,7 @@
get() = swipeableState.targetValue
/**
- * The current position (in pixels) of the drawer sheet.
+ * The current position (in pixels) of the drawer container.
*/
@Suppress("EXPERIMENTAL_ANNOTATION_ON_WRONG_TARGET")
@ExperimentalMaterial3Api
@@ -222,33 +238,31 @@
* Modal navigation drawers block interaction with the rest of an app’s content with a scrim.
* They are elevated above most of the app’s UI and don’t affect the screen’s layout grid.
*
- * @sample androidx.compose.material3.samples.NavigationDrawerSample
+ * @sample androidx.compose.material3.samples.ModalNavigationDrawerSample
*
* @param drawerContent composable that represents content inside the drawer
* @param modifier optional modifier for the drawer
* @param drawerState state of the drawer
* @param gesturesEnabled whether or not drawer can be interacted by gestures
- * @param drawerShape shape of the drawer sheet
+ * @param drawerShape shape of the drawer container
* @param drawerTonalElevation Affects the alpha of the color overlay applied on the container color
- * of the drawer sheet.
- * @param drawerContainerColor container color to be used for the drawer sheet
- * @param drawerContentColor color of the content to use inside the drawer sheet. Defaults to
+ * of the drawer container.
+ * @param drawerContainerColor container color to be used for the drawer container
+ * @param drawerContentColor color of the content to use inside the drawer container. Defaults to
* either the matching content color for [drawerContainerColor], or, if it is not a color from
* the theme, this will keep the same value set above this Surface.
* @param scrimColor color of the scrim that obscures content when the drawer is open
* @param content content of the rest of the UI
- *
- * @throws IllegalStateException when parent has [Float.POSITIVE_INFINITY] width
*/
@Composable
@ExperimentalMaterial3Api
-fun NavigationDrawer(
+fun ModalNavigationDrawer(
drawerContent: @Composable ColumnScope.() -> Unit,
modifier: Modifier = Modifier,
drawerState: DrawerState = rememberDrawerState(DrawerValue.Closed),
gesturesEnabled: Boolean = true,
drawerShape: Shape = RoundedCornerShape(0.dp, 16.dp, 16.dp, 0.dp),
- drawerTonalElevation: Dp = DrawerDefaults.Elevation,
+ drawerTonalElevation: Dp = DrawerDefaults.ModalDrawerElevation,
drawerContainerColor: Color = NavigationDrawerTokens.ContainerColor.toColor(),
drawerContentColor: Color = contentColorFor(drawerContainerColor),
scrimColor: Color = DrawerDefaults.scrimColor,
@@ -318,22 +332,454 @@
}
}
+@Composable
+@ExperimentalMaterial3Api
+@Deprecated(
+ "NavigationDrawer has been renamed to ModalNavigationDrawer to better specify " +
+ "its modal nature", replaceWith = ReplaceWith(
+ "ModalNavigationDrawer(drawerContent,\n" +
+ " modifier,\n" +
+ " drawerState,\n" +
+ " gesturesEnabled,\n" +
+ " drawerShape,\n" +
+ " drawerTonalElevation,\n" +
+ " drawerContainerColor,\n" +
+ " drawerContentColor,\n" +
+ " scrimColor,\n" +
+ " content)"
+ )
+)
+fun NavigationDrawer(
+ drawerContent: @Composable ColumnScope.() -> Unit,
+ modifier: Modifier = Modifier,
+ drawerState: DrawerState = rememberDrawerState(DrawerValue.Closed),
+ gesturesEnabled: Boolean = true,
+ drawerShape: Shape = RoundedCornerShape(0.dp, 16.dp, 16.dp, 0.dp),
+ drawerTonalElevation: Dp = DrawerDefaults.ModalDrawerElevation,
+ drawerContainerColor: Color = NavigationDrawerTokens.ContainerColor.toColor(),
+ drawerContentColor: Color = contentColorFor(drawerContainerColor),
+ scrimColor: Color = DrawerDefaults.scrimColor,
+ content: @Composable () -> Unit
+) {
+ ModalNavigationDrawer(
+ drawerContent,
+ modifier,
+ drawerState,
+ gesturesEnabled,
+ drawerShape,
+ drawerTonalElevation,
+ drawerContainerColor,
+ drawerContentColor,
+ scrimColor,
+ content
+ )
+}
+
+// TODO(b/218286829): Add spec image
/**
- * Object to hold default values for [NavigationDrawer]
+ * Material Design navigation drawer.
+ *
+ * Standard navigation drawers provide access to drawer destinations and app content on desktop and
+ * tablet screens. They’re often next to app content and affect the screen’s layout grid.
+ *
+ * Dismissible standard drawers can be used for layouts that prioritize content (such as a
+ * photo gallery) or for apps where users are unlikely to switch destinations often. They should
+ * use a visible navigation menu icon to open and close the drawer.
+ *
+ * @sample androidx.compose.material3.samples.DismissibleNavigationDrawerSample
+ *
+ * @param drawerContent composable that represents content inside the drawer
+ * @param modifier optional modifier for the drawer
+ * @param drawerState state of the drawer
+ * @param gesturesEnabled whether or not drawer can be interacted by gestures
+ * @param drawerShape shape of the drawer container
+ * @param drawerTonalElevation Affects the alpha of the color overlay applied on the container color
+ * of the drawer container.
+ * @param drawerContainerColor container color to be used for the drawer container
+ * @param drawerContentColor color of the content to use inside the drawer container. Defaults to
+ * either the matching content color for [drawerContainerColor], or, if it is not a color from
+ * the theme, this will keep the same value set above this Surface.
+ * @param content content of the rest of the UI
+ */
+@Composable
+@ExperimentalMaterial3Api
+fun DismissibleNavigationDrawer(
+ drawerContent: @Composable ColumnScope.() -> Unit,
+ modifier: Modifier = Modifier,
+ drawerState: DrawerState = rememberDrawerState(DrawerValue.Closed),
+ gesturesEnabled: Boolean = true,
+ drawerShape: Shape = RectangleShape,
+ drawerTonalElevation: Dp = DrawerDefaults.DismissibleDrawerElevation,
+ drawerContainerColor: Color = MaterialTheme.colorScheme.surface,
+ drawerContentColor: Color = contentColorFor(drawerContainerColor),
+ content: @Composable () -> Unit
+) {
+ val scope = rememberCoroutineScope()
+ val drawerWidth = NavigationDrawerTokens.ContainerWidth
+ val drawerWidthPx = with(LocalDensity.current) { drawerWidth.toPx() }
+ val minValue = -drawerWidthPx
+ val maxValue = 0f
+
+ val anchors = mapOf(minValue to DrawerValue.Closed, maxValue to DrawerValue.Open)
+ val isRtl = LocalLayoutDirection.current == LayoutDirection.Rtl
+ Box(
+ modifier.swipeable(
+ state = drawerState.swipeableState,
+ anchors = anchors,
+ thresholds = { _, _ -> FractionalThreshold(0.5f) },
+ orientation = Orientation.Horizontal,
+ enabled = gesturesEnabled,
+ reverseDirection = isRtl,
+ velocityThreshold = DrawerVelocityThreshold,
+ resistance = null
+ )
+ ) {
+ Layout(content = {
+ val navigationMenu = getString(Strings.NavigationMenu)
+ Surface(
+ modifier = Modifier
+ .sizeIn(maxWidth = drawerWidth)
+ .semantics {
+ paneTitle = navigationMenu
+ if (drawerState.isOpen) {
+ dismiss {
+ if (
+ drawerState.swipeableState
+ .confirmStateChange(DrawerValue.Closed)
+ ) {
+ scope.launch { drawerState.close() }
+ }; true
+ }
+ }
+ },
+ shape = drawerShape,
+ color = drawerContainerColor,
+ contentColor = drawerContentColor,
+ tonalElevation = drawerTonalElevation
+ ) {
+ Column(Modifier.fillMaxSize(), content = drawerContent)
+ }
+ Box {
+ content()
+ }
+ }) { measurables, constraints ->
+ val sheetPlaceable = measurables[0].measure(constraints)
+ val contentPlaceable = measurables[1].measure(constraints)
+ layout(contentPlaceable.width, contentPlaceable.height) {
+ contentPlaceable.placeRelative(
+ sheetPlaceable.width + drawerState.offset.value.roundToInt(),
+ 0
+ )
+ sheetPlaceable.placeRelative(drawerState.offset.value.roundToInt(), 0)
+ }
+ }
+ }
+}
+
+// TODO(b/218286829): Add spec image
+/**
+ * Material Design navigation permanent drawer.
+ *
+ * Standard navigation drawers provide access to drawer destinations and app content on desktop
+ * and tablet screens. They’re often next to app content and affect the screen’s layout grid.
+ *
+ * The permanent navigation drawer is always visible and usually used for frequently switching
+ * destinations. On mobile screens, use [ModalNavigationDrawer] instead.
+ *
+ * @sample androidx.compose.material3.samples.PermanentNavigationDrawerSample
+ *
+ * @param drawerContent composable that represents content inside the drawer
+ * @param modifier optional modifier for the drawer
+ * @param drawerShape shape of the drawer container
+ * @param drawerTonalElevation Affects the alpha of the color overlay applied on the container color
+ * of the drawer container.
+ * @param drawerContainerColor container color to be used for the drawer container
+ * @param drawerContentColor color of the content to use inside the drawer container. Defaults to
+ * either the matching content color for [drawerContainerColor], or, if it is not a color from
+ * the theme, this will keep the same value set above this Surface.
+ * @param content content of the rest of the UI
+ */
+@ExperimentalMaterial3Api
+@Composable
+fun PermanentNavigationDrawer(
+ drawerContent: @Composable ColumnScope.() -> Unit,
+ modifier: Modifier = Modifier,
+ drawerShape: Shape = RectangleShape,
+ drawerTonalElevation: Dp = DrawerDefaults.PermanentDrawerElevation,
+ drawerContainerColor: Color = MaterialTheme.colorScheme.surface,
+ drawerContentColor: Color = contentColorFor(drawerContainerColor),
+ content: @Composable () -> Unit
+) {
+ val drawerWidth = NavigationDrawerTokens.ContainerWidth
+ Row(modifier.fillMaxSize()) {
+ val navigationMenu = getString(Strings.NavigationMenu)
+ Surface(
+ modifier = Modifier
+ .sizeIn(maxWidth = drawerWidth)
+ .semantics {
+ paneTitle = navigationMenu
+ },
+ shape = drawerShape,
+ color = drawerContainerColor,
+ contentColor = drawerContentColor,
+ tonalElevation = drawerTonalElevation
+ ) {
+ Column(Modifier.fillMaxSize(), content = drawerContent)
+ }
+ Box {
+ content()
+ }
+ }
+}
+
+/**
+ * Object to hold default values for [ModalNavigationDrawer]
*/
@ExperimentalMaterial3Api
object DrawerDefaults {
/**
- * Default Elevation for drawer sheet as specified in material specs
+ * Default Elevation for drawer container in the [ModalNavigationDrawer] as specified in
+ * material specs
*/
- val Elevation = NavigationDrawerTokens.ModalContainerElevation
+ val ModalDrawerElevation = NavigationDrawerTokens.ModalContainerElevation
+
+ /**
+ * Default Elevation for drawer container in the [PermanentNavigationDrawer] as specified in
+ * material specs
+ */
+ val PermanentDrawerElevation = NavigationDrawerTokens.StandardContainerElevation
+
+ /**
+ * Default Elevation for drawer container in the [DismissibleNavigationDrawer] as specified in
+ * material specs
+ */
+ val DismissibleDrawerElevation = NavigationDrawerTokens.StandardContainerElevation
val scrimColor: Color
@Composable
get() = PaletteTokens.NeutralVariant0.copy(alpha = NavigationDrawerTokens.ScrimOpacity)
}
+/**
+ * Material Design navigation drawer item.
+ *
+ * A [NavigationDrawerItem] represents a destination within drawers, either [ModalNavigationDrawer],
+ * [PermanentNavigationDrawer] or [DismissibleNavigationDrawer].
+ *
+ * @sample androidx.compose.material3.samples.ModalNavigationDrawerSample
+ *
+ * @param label text label for this item
+ * @param selected whether this item is selected
+ * @param onClick the callback to be invoked when this item is clicked
+ * @param modifier optional [Modifier] for this item
+ * @param icon optional icon for this item, typically an [Icon]
+ * @param badge optional badge to show on this item from the end side
+ * @param colors the various colors used in elements of this item
+ * @param interactionSource the [MutableInteractionSource] representing the stream of [Interaction]s
+ * for this item. You can create and pass in your own remembered [MutableInteractionSource] if
+ * you want to observe [Interaction]s and customize the appearance / behavior of this item in
+ * different [Interaction]s.
+ */
+@Composable
+@ExperimentalMaterial3Api
+fun NavigationDrawerItem(
+ label: @Composable () -> Unit,
+ selected: Boolean,
+ onClick: () -> Unit,
+ modifier: Modifier = Modifier,
+ icon: (@Composable () -> Unit)? = null,
+ badge: (@Composable () -> Unit)? = null,
+ shape: Shape = NavigationDrawerTokens.ActiveIndicatorShape,
+ colors: NavigationDrawerItemColors = NavigationDrawerItemDefaults.colors(),
+ interactionSource: MutableInteractionSource = remember { MutableInteractionSource() }
+) {
+ Surface(
+ shape = shape,
+ color = colors.containerColor(selected).value,
+ interactionSource = interactionSource,
+ modifier = modifier
+ .height(NavigationDrawerTokens.ActiveIndicatorHeight)
+ .fillMaxWidth()
+ .selectable(
+ selected = selected,
+ onClick = onClick,
+ interactionSource = interactionSource,
+ role = Role.Tab,
+ indication = null
+ )
+ ) {
+ Row(
+ Modifier.padding(start = 16.dp, end = 24.dp),
+ verticalAlignment = Alignment.CenterVertically
+ ) {
+ if (icon != null) {
+ val iconColor = colors.iconColor(selected).value
+ CompositionLocalProvider(LocalContentColor provides iconColor, content = icon)
+ Spacer(Modifier.width(12.dp))
+ }
+ Box(Modifier.weight(1f)) {
+ val labelColor = colors.textColor(selected).value
+ CompositionLocalProvider(LocalContentColor provides labelColor, content = label)
+ }
+ if (badge != null) {
+ Spacer(Modifier.width(12.dp))
+ val badgeColor = colors.badgeColor(selected).value
+ CompositionLocalProvider(LocalContentColor provides badgeColor, content = badge)
+ }
+ }
+ }
+}
+
+/** Represents the colors of the various elements of a drawer item. */
+@Stable
+@ExperimentalMaterial3Api
+interface NavigationDrawerItemColors {
+
+ /**
+ * Represents the icon color for this item, depending on whether it is [selected].
+ *
+ * @param selected whether the item is selected
+ */
+ @Composable
+ fun iconColor(selected: Boolean): State<Color>
+
+ /**
+ * Represents the text color for this item, depending on whether it is [selected].
+ *
+ * @param selected whether the item is selected
+ */
+ @Composable
+ fun textColor(selected: Boolean): State<Color>
+
+ /**
+ * Represents the badge color for this item, depending on whether it is [selected].
+ *
+ * @param selected whether the item is selected
+ */
+ @Composable
+ fun badgeColor(selected: Boolean): State<Color>
+
+ /**
+ * Represents the container color for this item, depending on whether it is [selected].
+ *
+ * @param selected whether the item is selected
+ */
+ @Composable
+ fun containerColor(selected: Boolean): State<Color>
+}
+
+/** Defaults used in [NavigationDrawerItem]. */
+@ExperimentalMaterial3Api
+object NavigationDrawerItemDefaults {
+
+ /**
+ * Creates a [NavigationDrawerItemColors] with the provided colors according to the Material
+ * specification.
+ *
+ * @param selectedContainerColor the color to use for the background of the item when selected
+ * @param unselectedContainerColor the color to use for the background of the item when
+ * unselected
+ * @param selectedIconColor the color to use for the icon when the item is selected.
+ * @param unselectedIconColor the color to use for the icon when the item is unselected.
+ * @param selectedTextColor the color to use for the text label when the item is selected.
+ * @param unselectedTextColor the color to use for the text label when the item is unselected.
+ * @param selectedBadgeColor the color to use for the badge when the item is selected.
+ * @param unselectedBadgeColor the color to use for the badge when the item is unselected.
+ *
+ * @return the resulting [NavigationDrawerItemColors] used for [NavigationDrawerItem]
+ */
+ @Composable
+ fun colors(
+ selectedContainerColor: Color = NavigationDrawerTokens.ActiveIndicatorColor.toColor(),
+ unselectedContainerColor: Color = NavigationDrawerTokens.ContainerColor.toColor(),
+ selectedIconColor: Color = NavigationDrawerTokens.ActiveIconColor.toColor(),
+ unselectedIconColor: Color = NavigationDrawerTokens.InactiveIconColor.toColor(),
+ selectedTextColor: Color = NavigationDrawerTokens.ActiveLabelTextColor.toColor(),
+ unselectedTextColor: Color = NavigationDrawerTokens.InactiveLabelTextColor.toColor(),
+ selectedBadgeColor: Color = selectedTextColor,
+ unselectedBadgeColor: Color = unselectedTextColor,
+ ): NavigationDrawerItemColors = DefaultDrawerItemsColor(
+ selectedIconColor,
+ unselectedIconColor,
+ selectedTextColor,
+ unselectedTextColor,
+ selectedContainerColor,
+ unselectedContainerColor,
+ selectedBadgeColor,
+ unselectedBadgeColor
+ )
+
+ /**
+ * Default external padding for a [NavigationDrawerItem] according to material spec.
+ */
+ val ItemPadding = PaddingValues(horizontal = 12.dp)
+}
+
+@OptIn(ExperimentalMaterial3Api::class)
+private class DefaultDrawerItemsColor(
+ val selectedIconColor: Color,
+ val unselectedIconColor: Color,
+ val selectedTextColor: Color,
+ val unselectedTextColor: Color,
+ val selectedContainerColor: Color,
+ val unselectedContainerColor: Color,
+ val selectedBadgeColor: Color,
+ val unselectedBadgeColor: Color
+) : NavigationDrawerItemColors {
+ @Composable
+ override fun iconColor(selected: Boolean): State<Color> {
+ return rememberUpdatedState(if (selected) selectedIconColor else unselectedIconColor)
+ }
+
+ @Composable
+ override fun textColor(selected: Boolean): State<Color> {
+ return rememberUpdatedState(if (selected) selectedTextColor else unselectedTextColor)
+ }
+
+ @Composable
+ override fun containerColor(selected: Boolean): State<Color> {
+ return rememberUpdatedState(
+ if (selected) selectedContainerColor else unselectedContainerColor
+ )
+ }
+
+ @Composable
+ override fun badgeColor(selected: Boolean): State<Color> {
+ return rememberUpdatedState(
+ if (selected) selectedBadgeColor else unselectedBadgeColor
+ )
+ }
+
+ override fun equals(other: Any?): Boolean {
+ if (this === other) return true
+ if (other !is DefaultDrawerItemsColor) return false
+
+ if (selectedIconColor != other.selectedIconColor) return false
+ if (unselectedIconColor != other.unselectedIconColor) return false
+ if (selectedTextColor != other.selectedTextColor) return false
+ if (unselectedTextColor != other.unselectedTextColor) return false
+ if (selectedContainerColor != other.selectedContainerColor) return false
+ if (unselectedContainerColor != other.unselectedContainerColor) return false
+ if (selectedBadgeColor != other.selectedBadgeColor) return false
+ if (unselectedBadgeColor != other.unselectedBadgeColor) return false
+
+ return true
+ }
+
+ override fun hashCode(): Int {
+ var result = selectedIconColor.hashCode()
+ result = 31 * result + unselectedIconColor.hashCode()
+ result = 31 * result + selectedTextColor.hashCode()
+ result = 31 * result + unselectedTextColor.hashCode()
+ result = 31 * result + selectedContainerColor.hashCode()
+ result = 31 * result + unselectedContainerColor.hashCode()
+ result = 31 * result + selectedBadgeColor.hashCode()
+ result = 31 * result + unselectedBadgeColor.hashCode()
+ return result
+ }
+}
+
private fun calculateFraction(a: Float, b: Float, pos: Float) =
((pos - a) / (b - a)).coerceIn(0f, 1f)
@@ -365,7 +811,6 @@
}
}
-private val EndDrawerPadding = 56.dp
private val DrawerVelocityThreshold = 400.dp
// TODO: b/177571613 this should be a proper decay settling
diff --git a/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/snapshots/Snapshot.kt b/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/snapshots/Snapshot.kt
index 22cd1fc..4d63c1a 100644
--- a/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/snapshots/Snapshot.kt
+++ b/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/snapshots/Snapshot.kt
@@ -75,7 +75,9 @@
*/
open fun dispose() {
disposed = true
- releasePinnedSnapshot()
+ sync {
+ releasePinnedSnapshotLocked()
+ }
}
/**
@@ -134,12 +136,13 @@
internal var disposed = false
/*
- *
+ * Handle to use when unpinning this snapshot. -1 if this snapshot has been unpinned.
*/
- @Suppress("LeakingThis")
private var pinningTrackingHandle =
if (id != INVALID_SNAPSHOT) trackPinning(id, invalid) else -1
+ internal inline val isPinned get() = pinningTrackingHandle >= 0
+
/*
* The read observer for the snapshot if there is one.
*/
@@ -184,22 +187,44 @@
/**
* Closes the snapshot by removing the snapshot id (an any previous id's) from the list of
- * open snapshots.
+ * open snapshots and unpinning snapshots that no longer are referenced by this snapshot.
*/
- internal open fun close() {
+ internal fun closeAndReleasePinning() {
sync {
- openSnapshots = openSnapshots.clear(id)
- releasePinnedSnapshot()
+ closeLocked()
+ releasePinnedSnapshotsForCloseLocked()
}
}
+ /**
+ * Closes the snapshot by removing the snapshot id (and any previous ids) from the list of
+ * open snapshots. Does not release pinned snapshots. See [releasePinnedSnapshotsForCloseLocked]
+ * for the second half of [closeAndReleasePinning].
+ *
+ * Call while holding a `sync {}` lock.
+ */
+ internal open fun closeLocked() {
+ openSnapshots = openSnapshots.clear(id)
+ }
+
+ /**
+ * Releases all pinned snapshots required to perform a clean [closeAndReleasePinning].
+ *
+ * Call while holding a `sync {}` lock.
+ *
+ * See [closeAndReleasePinning], [closeLocked].
+ */
+ internal open fun releasePinnedSnapshotsForCloseLocked() {
+ releasePinnedSnapshotLocked()
+ }
+
internal fun validateNotDisposed() {
require(!disposed) { "Cannot use a disposed snapshot" }
}
- internal fun releasePinnedSnapshot() {
+ internal fun releasePinnedSnapshotLocked() {
if (pinningTrackingHandle >= 0) {
- releasePinning(pinningTrackingHandle)
+ releasePinningLocked(pinningTrackingHandle)
pinningTrackingHandle = -1
}
}
@@ -500,7 +525,7 @@
/**
* Pin the snapshot and invalid set.
*
- * @return returns a handle that should be passed to [releasePinning] when the snapshot closes or
+ * @return returns a handle that should be passed to [releasePinningLocked] when the snapshot closes or
* is disposed.
*/
internal fun trackPinning(id: Int, invalid: SnapshotIdSet): Int {
@@ -513,10 +538,8 @@
/**
* Release the [handle] returned by [trackPinning]
*/
-internal fun releasePinning(handle: Int) {
- sync {
- pinningTable.remove(handle)
- }
+internal fun releasePinningLocked(handle: Int) {
+ pinningTable.remove(handle)
}
/**
@@ -571,7 +594,7 @@
writeObserver: ((Any) -> Unit)? = null
): MutableSnapshot {
validateNotDisposed()
- validateNotApplied()
+ validateNotAppliedOrPinned()
return advance {
sync {
val newId = nextSnapshotId++
@@ -624,7 +647,7 @@
val (observers, globalModified) = sync {
validateOpen(this)
if (modified == null || modified.size == 0) {
- close()
+ closeLocked()
val previousGlobalSnapshot = currentGlobalSnapshot.get()
takeNewGlobalSnapshot(previousGlobalSnapshot, emptyLambda)
val globalModified = previousGlobalSnapshot.modified
@@ -634,15 +657,14 @@
emptyList<(Set<Any>, Snapshot) -> Unit>() to null
} else {
val previousGlobalSnapshot = currentGlobalSnapshot.get()
- val result = innerApply(
+ val result = innerApplyLocked(
nextSnapshotId,
optimisticMerges,
openSnapshots.clear(previousGlobalSnapshot.id)
)
if (result != SnapshotApplyResult.Success) return result
- // Close this snapshot
- close()
+ closeLocked()
// Take a new global snapshot that includes this one.
takeNewGlobalSnapshot(previousGlobalSnapshot, emptyLambda)
@@ -670,6 +692,13 @@
}
}
+ // Wait to release pinned snapshots until after running observers.
+ // This permits observers to safely take a nested snapshot of the one that was just applied
+ // before unpinning records that need to be retained in this case.
+ sync {
+ releasePinnedSnapshotsForCloseLocked()
+ }
+
return SnapshotApplyResult.Success
}
@@ -686,7 +715,7 @@
override fun takeNestedSnapshot(readObserver: ((Any) -> Unit)?): Snapshot {
validateNotDisposed()
- validateNotApplied()
+ validateNotAppliedOrPinned()
val previousId = id
return advance {
sync {
@@ -718,23 +747,31 @@
advance()
}
- override fun close() {
- sync {
- // Remove itself and previous ids from the open set.
- openSnapshots = openSnapshots.clear(id).andNot(previousIds)
- releasePinnedSnapshot()
- releasePreviouslyPinnedSnapshots()
- }
+ override fun closeLocked() {
+ // Remove itself and previous ids from the open set.
+ openSnapshots = openSnapshots.clear(id).andNot(previousIds)
+ }
+
+ override fun releasePinnedSnapshotsForCloseLocked() {
+ releasePreviouslyPinnedSnapshotsLocked()
+ super.releasePinnedSnapshotsForCloseLocked()
}
internal fun validateNotApplied() {
- require(!applied) {
+ check(!applied) {
"Unsupported operation on a snapshot that has been applied"
}
}
+ internal fun validateNotAppliedOrPinned() {
+ check(!applied || isPinned) {
+ "Unsupported operation on a disposed or applied snapshot"
+ }
+ }
+
/**
- * Abandon the snapshot.
+ * Abandon the snapshot. This does NOT [closeAndReleasePinning], which must be done
+ * as an additional step by callers.
*/
private fun abandon() {
val modified = modified
@@ -757,10 +794,10 @@
}
// The snapshot can now be closed.
- close()
+ closeAndReleasePinning()
}
- internal fun innerApply(
+ internal fun innerApplyLocked(
snapshotId: Int,
optimisticMerges: Map<StateRecord, StateRecord>?,
invalidSnapshots: SnapshotIdSet
@@ -851,12 +888,18 @@
internal inline fun <T> advance(block: () -> T): T {
recordPrevious(id)
return block().also {
- val previousId = id
- sync {
- id = nextSnapshotId++
- openSnapshots = openSnapshots.set(id)
+ // Only advance this snapshot if it's possible for it to be applied later,
+ // otherwise we don't need to bother.
+ // This simplifies tracking of open snapshots when an apply observer takes
+ // a nested snapshot of the snapshot that was just applied.
+ if (!applied && !disposed) {
+ val previousId = id
+ sync {
+ id = nextSnapshotId++
+ openSnapshots = openSnapshots.set(id)
+ }
+ invalid = invalid.addRange(previousId + 1, id)
}
- invalid = invalid.addRange(previousId + 1, id)
}
}
@@ -881,9 +924,9 @@
else previousPinnedSnapshots = pinned + handles
}
- internal fun releasePreviouslyPinnedSnapshots() {
+ internal fun releasePreviouslyPinnedSnapshotsLocked() {
for (index in previousPinnedSnapshots.indices) {
- releasePinning(previousPinnedSnapshots[index])
+ releasePinningLocked(previousPinnedSnapshots[index])
}
}
@@ -1117,7 +1160,7 @@
override fun nestedDeactivated(snapshot: Snapshot) {
if (--snapshots == 0) {
// A read-only snapshot can be just be closed as it has no modifications.
- close()
+ closeAndReleasePinning()
}
}
@@ -1155,7 +1198,7 @@
override fun dispose() {
if (!disposed) {
if (id != parent.id) {
- close()
+ closeAndReleasePinning()
}
parent.nestedDeactivated(this)
super.dispose()
@@ -1232,7 +1275,9 @@
error("Cannot apply the global snapshot directly. Call Snapshot.advanceGlobalSnapshot")
override fun dispose() {
- releasePinnedSnapshot()
+ sync {
+ releasePinnedSnapshotLocked()
+ }
}
}
@@ -1277,9 +1322,9 @@
sync {
validateOpen(this)
if (modified == null || modified.size == 0) {
- close()
+ closeAndReleasePinning()
} else {
- val result = innerApply(parent.id, optimisticMerges, parent.invalid)
+ val result = innerApplyLocked(parent.id, optimisticMerges, parent.invalid)
if (result != SnapshotApplyResult.Success) return result
// Add all modified objects in this set to the parent
diff --git a/compose/runtime/runtime/src/commonTest/kotlin/androidx/compose/runtime/snapshots/SnapshotTests.kt b/compose/runtime/runtime/src/commonTest/kotlin/androidx/compose/runtime/snapshots/SnapshotTests.kt
index 0e4e210..b51a20a 100644
--- a/compose/runtime/runtime/src/commonTest/kotlin/androidx/compose/runtime/snapshots/SnapshotTests.kt
+++ b/compose/runtime/runtime/src/commonTest/kotlin/androidx/compose/runtime/snapshots/SnapshotTests.kt
@@ -38,6 +38,7 @@
import kotlin.test.assertFailsWith
import kotlin.test.assertFalse
import kotlin.test.assertTrue
+import kotlin.test.fail
class SnapshotTests {
@Test
@@ -806,6 +807,84 @@
}
@Test
+ fun canTakeNestedSnapshotsFromApplyObserver() {
+ var takenSnapshot: Snapshot? = null
+ val observer = Snapshot.registerApplyObserver { _, snapshot ->
+ if (takenSnapshot != null) error("already took a nested snapshot")
+ takenSnapshot = snapshot.takeNestedSnapshot()
+ }
+
+ try {
+ var state by mutableStateOf("initial")
+ Snapshot.withMutableSnapshot {
+ state = "before observer snapshot"
+ }
+
+ state = "after observer snapshot"
+
+ val observerSnapshot = takenSnapshot ?: fail("snapshot was not taken by observer")
+
+ observerSnapshot.enter {
+ assertEquals("before observer snapshot", state)
+ }
+ } finally {
+ observer.dispose()
+ takenSnapshot?.dispose()
+ }
+ }
+
+ @Test
+ fun canTakeNestedMutableSnapshotsFromApplyObserver() {
+ var takenSnapshot: MutableSnapshot? = null
+ val observer = Snapshot.registerApplyObserver { _, snapshot ->
+ if (takenSnapshot != null) error("already took a nested snapshot")
+ takenSnapshot = (snapshot as? MutableSnapshot)
+ ?.takeNestedMutableSnapshot()
+ ?: error("Applied snapshot was not mutable")
+ }
+
+ try {
+ var state by mutableStateOf("initial")
+ Snapshot.withMutableSnapshot {
+ state = "before observer snapshot"
+ }
+
+ state = "after observer snapshot"
+
+ val observerSnapshot = takenSnapshot ?: fail("snapshot was not taken by observer")
+
+ observerSnapshot.enter {
+ assertEquals("before observer snapshot", state)
+ state = "change made by observer snapshot"
+ }
+
+ assertFalse(observerSnapshot.apply().succeeded,
+ "applying observer snapshot with conflicting change")
+ } finally {
+ observer.dispose()
+ takenSnapshot?.dispose()
+ }
+ }
+
+ @Test
+ fun cannotTakeSnapshotOfClosedSnapshotAfterApplyReturns() {
+ val snapshot = takeMutableSnapshot()
+ @Suppress("ASSIGNED_BUT_NEVER_ACCESSED_VARIABLE")
+ var state by mutableStateOf("initial")
+
+ try {
+ snapshot.enter { state = "mutated" }
+ snapshot.apply().check()
+ snapshot.takeNestedSnapshot().dispose()
+ fail("taking a nested snapshot of an applied snapshot did not throw")
+ } catch (ise: IllegalStateException) {
+ // expected
+ } finally {
+ snapshot.dispose()
+ }
+ }
+
+ @Test
fun testRecordsAreReusedCorrectly() {
val value = mutableStateOf(0)
Snapshot.withMutableSnapshot { value.value++ }
diff --git a/compose/ui/ui/build.gradle b/compose/ui/ui/build.gradle
index fe0d2ec..fe009d6 100644
--- a/compose/ui/ui/build.gradle
+++ b/compose/ui/ui/build.gradle
@@ -71,7 +71,7 @@
testImplementation(libs.truth)
testImplementation(libs.mockitoCore)
testImplementation(libs.mockitoKotlin)
- testImplementation("org.robolectric:robolectric:4.6.1") // TODO(b/209063122): fix tests to work with SDK 31 and robolectric 4.7
+ testImplementation(libs.robolectric)
testImplementation(project(":compose:ui:ui-test-junit4"))
testImplementation(project(":compose:test-utils"))
diff --git a/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/AndroidAccessibilityTest.kt b/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/AndroidAccessibilityTest.kt
index f7171bc..3bd6359 100644
--- a/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/AndroidAccessibilityTest.kt
+++ b/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/AndroidAccessibilityTest.kt
@@ -1708,6 +1708,29 @@
}
@Test
+ fun testSemanticsHitTest_clearAndSet() {
+ val outertag = "outerbox"
+ val innertag = "innerbox"
+ container.setContent {
+ Box(Modifier.size(100.dp).clickable {}.testTag(outertag).clearAndSetSemantics {}) {
+ Box(Modifier.size(100.dp).clickable {}.testTag(innertag)) {
+ BasicText("")
+ }
+ }
+ }
+
+ val outerNode = rule.onNodeWithTag(outertag).fetchSemanticsNode("")
+ val innerNode = rule.onNodeWithTag(innertag, true).fetchSemanticsNode("")
+ val bounds = innerNode.boundsInRoot
+
+ val hitNodeId = delegate.hitTestSemanticsAt(
+ bounds.left + bounds.width / 2,
+ bounds.top + bounds.height / 2
+ )
+ assertEquals(outerNode.id, hitNodeId)
+ }
+
+ @Test
@SdkSuppress(maxSdkVersion = Build.VERSION_CODES.P)
fun testViewInterop_findViewByAccessibilityId() {
val androidViewTag = "androidView"
diff --git a/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/layout/SubcomposeLayoutTest.kt b/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/layout/SubcomposeLayoutTest.kt
index dc297f4..f929af9 100644
--- a/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/layout/SubcomposeLayoutTest.kt
+++ b/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/layout/SubcomposeLayoutTest.kt
@@ -1797,6 +1797,26 @@
}
}
+ @Test
+ fun disposeSecondPrecomposedItem() {
+ // it is a regression from b/218668336. the assertion was incorrectly checking
+ // for the ranges so disposing the second active precomposed node was crashing.
+ val state = SubcomposeLayoutState(SubcomposeSlotReusePolicy(0))
+
+ composeItems(state, mutableStateOf(emptyList()))
+
+ rule.runOnIdle {
+ state.precompose(0) { ItemContent(0) }
+ val handle = state.precompose(1) { ItemContent(1) }
+ handle.dispose()
+ }
+
+ assertNodes(
+ exists = /*prefetch*/ listOf(0),
+ doesNotExist = /*disposed*/ listOf(1)
+ )
+ }
+
private fun composeItems(
state: SubcomposeLayoutState,
items: MutableState<List<Int>>
diff --git a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/layout/SubcomposeLayout.kt b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/layout/SubcomposeLayout.kt
index 703064d..b2847d2 100644
--- a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/layout/SubcomposeLayout.kt
+++ b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/layout/SubcomposeLayout.kt
@@ -565,7 +565,8 @@
if (node != null) {
check(precomposedCount > 0)
val itemIndex = root.foldedChildren.indexOf(node)
- check(itemIndex <= root.foldedChildren.size - precomposedCount)
+ // make sure this item is in the precomposed items range
+ check(itemIndex >= root.foldedChildren.size - precomposedCount)
// move this item into the reusable section
reusableCount++
precomposedCount--
diff --git a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/DelegatingLayoutNodeWrapper.kt b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/DelegatingLayoutNodeWrapper.kt
index 17a1516..c4f4c9a 100644
--- a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/DelegatingLayoutNodeWrapper.kt
+++ b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/DelegatingLayoutNodeWrapper.kt
@@ -104,6 +104,7 @@
}
}
}
+ onMeasured()
return this
}
@@ -145,6 +146,4 @@
override fun minIntrinsicHeight(width: Int) = wrapped.minIntrinsicHeight(width)
override fun maxIntrinsicHeight(width: Int) = wrapped.maxIntrinsicHeight(width)
-
- override val parentData: Any? get() = wrapped.parentData
}
diff --git a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/EntityList.kt b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/EntityList.kt
index 588d2a9..a573ee2 100644
--- a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/EntityList.kt
+++ b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/EntityList.kt
@@ -16,9 +16,13 @@
package androidx.compose.ui.node
+import androidx.compose.ui.ExperimentalComposeUiApi
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.DrawModifier
import androidx.compose.ui.input.pointer.PointerInputModifier
+import androidx.compose.ui.layout.OnPlacedModifier
+import androidx.compose.ui.layout.OnRemeasuredModifier
+import androidx.compose.ui.layout.ParentDataModifier
import androidx.compose.ui.semantics.SemanticsEntity
import androidx.compose.ui.semantics.SemanticsModifier
@@ -46,6 +50,16 @@
if (modifier is SemanticsModifier) {
add(SemanticsEntity(layoutNodeWrapper, modifier), SemanticsEntityType.index)
}
+ if (modifier is ParentDataModifier) {
+ add(SimpleEntity(layoutNodeWrapper, modifier), ParentDataEntityType.index)
+ }
+ @OptIn(ExperimentalComposeUiApi::class)
+ if (modifier is OnPlacedModifier) {
+ add(SimpleEntity(layoutNodeWrapper, modifier), OnPlacedEntityType.index)
+ }
+ if (modifier is OnRemeasuredModifier) {
+ add(SimpleEntity(layoutNodeWrapper, modifier), RemeasureEntityType.index)
+ }
}
private fun <T : LayoutNodeEntity<T, *>> add(entity: T, index: Int) {
@@ -122,7 +136,14 @@
val DrawEntityType = EntityType<DrawEntity, DrawModifier>(0)
val PointerInputEntityType = EntityType<PointerInputEntity, PointerInputModifier>(1)
val SemanticsEntityType = EntityType<SemanticsEntity, SemanticsModifier>(2)
+ val ParentDataEntityType =
+ EntityType<SimpleEntity<ParentDataModifier>, ParentDataModifier>(3)
+ @OptIn(ExperimentalComposeUiApi::class)
+ val OnPlacedEntityType =
+ EntityType<SimpleEntity<OnPlacedModifier>, OnPlacedModifier>(4)
+ val RemeasureEntityType =
+ EntityType<SimpleEntity<OnRemeasuredModifier>, OnRemeasuredModifier>(5)
- private const val TypeCount = 3
+ private const val TypeCount = 6
}
}
diff --git a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/InnerPlaceable.kt b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/InnerPlaceable.kt
index fdb4ab6..3afa7f0 100644
--- a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/InnerPlaceable.kt
+++ b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/InnerPlaceable.kt
@@ -54,12 +54,10 @@
layoutNode.measureScope.measure(layoutNode.children, constraints)
}
layoutNode.handleMeasureResult(measureResult)
+ onMeasured()
return this
}
- override val parentData: Any?
- get() = null
-
override fun findPreviousFocusWrapper() = wrappedBy?.findPreviousFocusWrapper()
override fun findNextFocusWrapper(excludeDeactivated: Boolean): ModifiedFocusNode? = null
@@ -154,13 +152,15 @@
var inLayer = isInLayer
var hitTestChildren = false
- if (withinLayerBounds(pointerPosition)) {
- hitTestChildren = true
- } else if (isTouchEvent &&
- distanceInMinimumTouchTarget(pointerPosition, minimumTouchTargetSize).isFinite()
- ) {
- inLayer = false
- hitTestChildren = true
+ if (hitTestSource.shouldHitTestChildren(layoutNode)) {
+ if (withinLayerBounds(pointerPosition)) {
+ hitTestChildren = true
+ } else if (isTouchEvent &&
+ distanceInMinimumTouchTarget(pointerPosition, minimumTouchTargetSize).isFinite()
+ ) {
+ inLayer = false
+ hitTestChildren = true
+ }
}
if (hitTestChildren) {
diff --git a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/LayoutNode.kt b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/LayoutNode.kt
index be57631..7fc5587 100644
--- a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/LayoutNode.kt
+++ b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/LayoutNode.kt
@@ -43,9 +43,6 @@
import androidx.compose.ui.layout.MeasureScope
import androidx.compose.ui.layout.ModifierInfo
import androidx.compose.ui.layout.OnGloballyPositionedModifier
-import androidx.compose.ui.layout.OnPlacedModifier
-import androidx.compose.ui.layout.OnRemeasuredModifier
-import androidx.compose.ui.layout.ParentDataModifier
import androidx.compose.ui.layout.Placeable
import androidx.compose.ui.layout.Remeasurement
import androidx.compose.ui.layout.RemeasurementModifier
@@ -666,6 +663,10 @@
toWrap.entities.add(toWrap, mod)
+ if (mod is OnGloballyPositionedModifier) {
+ getOrCreateOnPositionedCallbacks() += toWrap to mod
+ }
+
// Re-use the layoutNodeWrapper if possible.
reuseLayoutNodeWrapper(mod, toWrap)?.let {
return@foldOut it
@@ -727,26 +728,6 @@
.initialize()
.assignChained(toWrap)
}
- if (mod is ParentDataModifier) {
- wrapper = ModifiedParentDataNode(wrapper, mod)
- .initialize()
- .assignChained(toWrap)
- }
- if (mod is OnRemeasuredModifier) {
- wrapper = RemeasureModifierWrapper(wrapper, mod)
- .initialize()
- .assignChained(toWrap)
- }
- if (mod is OnPlacedModifier) {
- wrapper = OnPlacedModifierWrapper(wrapper, mod)
- .initialize()
- .assignChained(toWrap)
- }
- if (mod is OnGloballyPositionedModifier) {
- wrapper = OnGloballyPositionedModifierWrapper(wrapper, mod)
- .initialize()
- .assignChained(toWrap)
- }
wrapper
}
@@ -816,10 +797,11 @@
/**
* List of all OnPositioned callbacks in the modifier chain.
*/
- private var onPositionedCallbacks: MutableVector<OnGloballyPositionedModifierWrapper>? = null
+ private var onPositionedCallbacks:
+ MutableVector<Pair<LayoutNodeWrapper, OnGloballyPositionedModifier>>? = null
internal fun getOrCreateOnPositionedCallbacks() = onPositionedCallbacks
- ?: mutableVectorOf<OnGloballyPositionedModifierWrapper>().also {
+ ?: mutableVectorOf<Pair<LayoutNodeWrapper, OnGloballyPositionedModifier>>().also {
onPositionedCallbacks = it
}
@@ -913,7 +895,7 @@
val onPositionedCallbacks = onPositionedCallbacks
return modifier.foldOut(false) { mod, hasNewCallback ->
hasNewCallback || mod is OnGloballyPositionedModifier &&
- (onPositionedCallbacks?.firstOrNull { mod == it.modifier } == null)
+ (onPositionedCallbacks?.firstOrNull { mod == it.second } == null)
}
}
@@ -1179,7 +1161,7 @@
return // it hasn't been placed, so don't make a call
}
onPositionedCallbacks?.forEach {
- it.modifier.onGloballyPositioned(it)
+ it.second.onGloballyPositioned(it.first)
}
}
diff --git a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/LayoutNodeAlignmentLines.kt b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/LayoutNodeAlignmentLines.kt
index 3e1c26c..94d7eef 100644
--- a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/LayoutNodeAlignmentLines.kt
+++ b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/LayoutNodeAlignmentLines.kt
@@ -122,7 +122,7 @@
position = wrapper.toParentPosition(position)
wrapper = wrapper.wrappedBy!!
if (wrapper == layoutNode.innerLayoutNodeWrapper) break
- if (alignmentLine in wrapper.providedAlignmentLines) {
+ if (alignmentLine in wrapper.measureResult.alignmentLines) {
val newPosition = wrapper[alignmentLine]
position = Offset(newPosition.toFloat(), newPosition.toFloat())
}
@@ -157,7 +157,7 @@
// Add alignment lines on the modifier of the child.
var wrapper = child.innerLayoutNodeWrapper.wrappedBy!!
while (wrapper != layoutNode.innerLayoutNodeWrapper) {
- wrapper.providedAlignmentLines.forEach { childLine ->
+ wrapper.measureResult.alignmentLines.keys.forEach { childLine ->
addAlignmentLine(childLine, wrapper[childLine], wrapper)
}
wrapper = wrapper.wrappedBy!!
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 1d69f8e..9086834 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
@@ -18,6 +18,7 @@
package androidx.compose.ui.node
+import androidx.compose.ui.ExperimentalComposeUiApi
import androidx.compose.ui.Modifier
import androidx.compose.ui.focus.FocusOrder
import androidx.compose.ui.focus.FocusState
@@ -40,11 +41,13 @@
import androidx.compose.ui.layout.Measurable
import androidx.compose.ui.layout.MeasureResult
import androidx.compose.ui.layout.MeasureScope
+import androidx.compose.ui.layout.ParentDataModifier
import androidx.compose.ui.layout.Placeable
import androidx.compose.ui.layout.VerticalAlignmentLine
import androidx.compose.ui.layout.findRoot
import androidx.compose.ui.layout.positionInRoot
import androidx.compose.ui.modifier.ModifierLocal
+import androidx.compose.ui.semantics.outerSemantics
import androidx.compose.ui.semantics.SemanticsEntity
import androidx.compose.ui.semantics.SemanticsModifier
import androidx.compose.ui.unit.Constraints
@@ -142,7 +145,21 @@
private var oldAlignmentLines: MutableMap<AlignmentLine, Int>? = null
override val providedAlignmentLines: Set<AlignmentLine>
- get() = _measureResult?.alignmentLines?.keys ?: emptySet()
+ get() {
+ var set: MutableSet<AlignmentLine>? = null
+ var wrapper: LayoutNodeWrapper? = this
+ while (wrapper != null) {
+ val alignmentLines = wrapper._measureResult?.alignmentLines
+ if (alignmentLines?.isNotEmpty() == true) {
+ if (set == null) {
+ set = mutableSetOf()
+ }
+ set.addAll(alignmentLines.keys)
+ }
+ wrapper = wrapper.wrapped
+ }
+ return set ?: emptySet()
+ }
/**
* Called when the width or height of [measureResult] change. The object instance pointed to
@@ -166,6 +183,22 @@
var zIndex: Float = 0f
protected set
+ override val parentData: Any?
+ get() = entities.head(EntityList.ParentDataEntityType).parentData
+
+ private val SimpleEntity<ParentDataModifier>?.parentData: Any?
+ get() = if (this == null) {
+ wrapped?.parentData
+ } else {
+ with(modifier) {
+ /**
+ * ParentData provided through the parentData node will override the data provided
+ * through a modifier.
+ */
+ measureScope.modifyParentData(next.parentData)
+ }
+ }
+
final override val parentLayoutCoordinates: LayoutCoordinates?
get() {
check(isAttached) { ExpectAttachedLayoutCoordinates }
@@ -209,6 +242,19 @@
return result
}
+ fun onMeasured() {
+ if (entities.has(EntityList.RemeasureEntityType)) {
+ val invokeRemeasureCallbacks = {
+ entities.forEach(EntityList.RemeasureEntityType) {
+ it.modifier.onRemeasured(measuredSize)
+ }
+ }
+ layoutNode.owner?.snapshotObserver?.withNoSnapshotReadObservation(
+ invokeRemeasureCallbacks
+ ) ?: invokeRemeasureCallbacks()
+ }
+ }
+
abstract fun calculateAlignmentLine(alignmentLine: AlignmentLine): Int
final override fun get(alignmentLine: AlignmentLine): Int {
@@ -287,7 +333,12 @@
wrapped?.draw(canvas)
}
- open fun onPlaced() {}
+ fun onPlaced() {
+ @OptIn(ExperimentalComposeUiApi::class)
+ entities.forEach(EntityList.OnPlacedEntityType) {
+ it.modifier.onPlaced(this)
+ }
+ }
// implementation of draw block passed to the OwnedLayer
@Suppress("LiftReturnOrAssignment")
@@ -1197,6 +1248,12 @@
fun interceptOutOfBoundsChildEvents(entity: T): Boolean
/**
+ * Returns false if the parent layout node has a state that suppresses
+ * hit testing of its children.
+ */
+ fun shouldHitTestChildren(parentLayoutNode: LayoutNode): Boolean
+
+ /**
* Calls a hit test on [layoutNode].
*/
fun childHitTest(
@@ -1236,6 +1293,8 @@
override fun interceptOutOfBoundsChildEvents(entity: PointerInputEntity) =
entity.modifier.pointerInputFilter.interceptOutOfBoundsChildEvents
+ override fun shouldHitTestChildren(parentLayoutNode: LayoutNode) = true
+
override fun childHitTest(
layoutNode: LayoutNode,
pointerPosition: Offset,
@@ -1256,6 +1315,10 @@
override fun interceptOutOfBoundsChildEvents(entity: SemanticsEntity) = false
+ override fun shouldHitTestChildren(parentLayoutNode: LayoutNode) =
+ parentLayoutNode.outerSemantics?.collapsedSemanticsConfiguration()
+ ?.isClearingSemantics != true
+
override fun childHitTest(
layoutNode: LayoutNode,
pointerPosition: Offset,
diff --git a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/ModifiedLayoutNode.kt b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/ModifiedLayoutNode.kt
index 69a1192..fae672fa 100644
--- a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/ModifiedLayoutNode.kt
+++ b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/ModifiedLayoutNode.kt
@@ -34,11 +34,15 @@
modifier: LayoutModifier
) : DelegatingLayoutNodeWrapper<LayoutModifier>(wrapped, modifier) {
- override fun measure(constraints: Constraints): Placeable = performingMeasure(constraints) {
- with(modifier) {
- measureResult = measureScope.measure(wrapped, constraints)
- this@ModifiedLayoutNode
+ override fun measure(constraints: Constraints): Placeable {
+ val placeable = performingMeasure(constraints) {
+ with(modifier) {
+ measureResult = measureScope.measure(wrapped, constraints)
+ this@ModifiedLayoutNode
+ }
}
+ onMeasured()
+ return placeable
}
override fun minIntrinsicWidth(height: Int): Int =
diff --git a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/ModifiedParentDataNode.kt b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/ModifiedParentDataNode.kt
deleted file mode 100644
index 521c2c0..0000000
--- a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/ModifiedParentDataNode.kt
+++ /dev/null
@@ -1,33 +0,0 @@
-/*
- * Copyright 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package androidx.compose.ui.node
-
-import androidx.compose.ui.layout.ParentDataModifier
-
-internal class ModifiedParentDataNode(
- wrapped: LayoutNodeWrapper,
- parentDataModifier: ParentDataModifier
-) : DelegatingLayoutNodeWrapper<ParentDataModifier>(wrapped, parentDataModifier) {
- override val parentData: Any?
- get() = with(modifier) {
- /**
- * ParentData provided through the parentData node will override the data provided
- * through a modifier
- */
- layoutNode.measureScope.modifyParentData(wrapped.parentData)
- }
-}
\ No newline at end of file
diff --git a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/OnGloballyPositionedModifierWrapper.kt b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/OnGloballyPositionedModifierWrapper.kt
deleted file mode 100644
index 930985e..0000000
--- a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/OnGloballyPositionedModifierWrapper.kt
+++ /dev/null
@@ -1,47 +0,0 @@
-/*
- * Copyright 2021 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package androidx.compose.ui.node
-
-import androidx.compose.ui.layout.AlignmentLine
-import androidx.compose.ui.layout.OnGloballyPositionedModifier
-
-/**
- * Wrapper around the [OnGloballyPositionedModifier].
- */
-internal class OnGloballyPositionedModifierWrapper(
- wrapped: LayoutNodeWrapper,
- modifier: OnGloballyPositionedModifier
-) : DelegatingLayoutNodeWrapper<OnGloballyPositionedModifier>(wrapped, modifier) {
-
- override fun onInitialize() {
- super.onInitialize()
- layoutNode.getOrCreateOnPositionedCallbacks() += this
- }
-
- override val providedAlignmentLines: Set<AlignmentLine>
- get() {
- val result = mutableSetOf<AlignmentLine>()
- layoutNode
- var wrapper = wrapped as LayoutNodeWrapper?
- while (wrapper != null) {
- result += wrapper.providedAlignmentLines
- if (wrapper == layoutNode.innerLayoutNodeWrapper) break
- wrapper = wrapper.wrapped
- }
- return result
- }
-}
diff --git a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/OnPlacedModifierWrapper.kt b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/OnPlacedModifierWrapper.kt
deleted file mode 100644
index 86e7928..0000000
--- a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/OnPlacedModifierWrapper.kt
+++ /dev/null
@@ -1,48 +0,0 @@
-/*
- * Copyright 2021 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package androidx.compose.ui.node
-
-import androidx.compose.ui.ExperimentalComposeUiApi
-import androidx.compose.ui.layout.AlignmentLine
-import androidx.compose.ui.layout.OnPlacedModifier
-
-/**
- * Wrapper around the [OnPlacedModifier].
- */
-@OptIn(ExperimentalComposeUiApi::class)
-internal class OnPlacedModifierWrapper constructor(
- wrapped: LayoutNodeWrapper,
- modifier: OnPlacedModifier
-) : DelegatingLayoutNodeWrapper<OnPlacedModifier>(wrapped, modifier) {
- override val providedAlignmentLines: Set<AlignmentLine>
- get() {
- val result = mutableSetOf<AlignmentLine>()
- layoutNode
- var wrapper = wrapped as LayoutNodeWrapper?
- while (wrapper != null) {
- result += wrapper.providedAlignmentLines
- if (wrapper == layoutNode.innerLayoutNodeWrapper) break
- wrapper = wrapper.wrapped
- }
- return result
- }
-
- override fun onPlaced() {
- // Invoke the onPlaced callback.
- modifier.onPlaced(this)
- }
-}
diff --git a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/RemeasureModifierWrapper.kt b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/RemeasureModifierWrapper.kt
deleted file mode 100644
index fdb9840..0000000
--- a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/RemeasureModifierWrapper.kt
+++ /dev/null
@@ -1,39 +0,0 @@
-/*
- * Copyright 2021 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package androidx.compose.ui.node
-
-import androidx.compose.ui.layout.OnRemeasuredModifier
-import androidx.compose.ui.layout.Placeable
-import androidx.compose.ui.unit.Constraints
-
-/**
- * Wrapper around the [OnRemeasuredModifier] to notify whenever a remeasurement happens.
- */
-internal class RemeasureModifierWrapper(
- wrapped: LayoutNodeWrapper,
- modifier: OnRemeasuredModifier
-) : DelegatingLayoutNodeWrapper<OnRemeasuredModifier>(wrapped, modifier) {
- override fun measure(constraints: Constraints): Placeable {
- val placeable = super.measure(constraints)
- val invokeRemeasureCallbacks = {
- modifier.onRemeasured(measuredSize)
- }
- layoutNode.owner?.snapshotObserver?.withNoSnapshotReadObservation(invokeRemeasureCallbacks)
- ?: invokeRemeasureCallbacks.invoke()
- return placeable
- }
-}
\ No newline at end of file
diff --git a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/SimpleEntity.kt b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/SimpleEntity.kt
new file mode 100644
index 0000000..4c471ff
--- /dev/null
+++ b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/SimpleEntity.kt
@@ -0,0 +1,27 @@
+/*
+ * Copyright 2022 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.node
+
+import androidx.compose.ui.Modifier
+
+/**
+ * A [LayoutNodeEntity] that only contains a [Modifier] and no logic
+ */
+internal class SimpleEntity<M : Modifier>(
+ layoutNodeWrapper: LayoutNodeWrapper,
+ modifier: M
+) : LayoutNodeEntity<SimpleEntity<M>, M>(layoutNodeWrapper, modifier)
\ No newline at end of file
diff --git a/core/OWNERS b/core/OWNERS
index 3a3ca6d..6a8e25d 100644
--- a/core/OWNERS
+++ b/core/OWNERS
@@ -8,7 +8,6 @@
# For fingerprint related files
jaggies@google.com
joshmccloskey@google.com
-kchyn@google.com
curtislb@google.com
# For text related files
diff --git a/core/core-i18n/OWNERS b/core/core-i18n/OWNERS
new file mode 100644
index 0000000..542c925
--- /dev/null
+++ b/core/core-i18n/OWNERS
@@ -0,0 +1 @@
+mnita@google.com
diff --git a/core/core-i18n/api/current.txt b/core/core-i18n/api/current.txt
new file mode 100644
index 0000000..e6f50d0
--- /dev/null
+++ b/core/core-i18n/api/current.txt
@@ -0,0 +1 @@
+// Signature format: 4.0
diff --git a/core/core-i18n/api/public_plus_experimental_current.txt b/core/core-i18n/api/public_plus_experimental_current.txt
new file mode 100644
index 0000000..e6f50d0
--- /dev/null
+++ b/core/core-i18n/api/public_plus_experimental_current.txt
@@ -0,0 +1 @@
+// Signature format: 4.0
diff --git a/core/core-i18n/api/res-current.txt b/core/core-i18n/api/res-current.txt
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/core/core-i18n/api/res-current.txt
diff --git a/core/core-i18n/api/restricted_current.txt b/core/core-i18n/api/restricted_current.txt
new file mode 100644
index 0000000..e6f50d0
--- /dev/null
+++ b/core/core-i18n/api/restricted_current.txt
@@ -0,0 +1 @@
+// Signature format: 4.0
diff --git a/core/core-i18n/build.gradle b/core/core-i18n/build.gradle
new file mode 100644
index 0000000..486fac0
--- /dev/null
+++ b/core/core-i18n/build.gradle
@@ -0,0 +1,37 @@
+/*
+ * Copyright (C) 2022 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.LibraryType
+
+plugins {
+ id("AndroidXPlugin")
+ id("com.android.library")
+ id("org.jetbrains.kotlin.android")
+}
+
+dependencies {
+ api(libs.kotlinStdlib)
+ // Add dependencies here
+}
+
+androidx {
+ name = "Core Internationalization"
+ type = LibraryType.PUBLISHED_LIBRARY
+ mavenVersion = LibraryVersions.CORE_I18N
+ mavenGroup = LibraryGroups.CORE
+ inceptionYear = "2022"
+ description = "This library provides functionality for good internationalization (messages, plurals, date / time formatting)."
+}
diff --git a/core/core-i18n/src/androidTest/AndroidManifest.xml b/core/core-i18n/src/androidTest/AndroidManifest.xml
new file mode 100644
index 0000000..c2e46aa1
--- /dev/null
+++ b/core/core-i18n/src/androidTest/AndroidManifest.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright 2022 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.core.i18n.test">
+
+</manifest>
diff --git a/core/core-i18n/src/main/AndroidManifest.xml b/core/core-i18n/src/main/AndroidManifest.xml
new file mode 100644
index 0000000..ccf8549
--- /dev/null
+++ b/core/core-i18n/src/main/AndroidManifest.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright 2022 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.core.i18n">
+
+</manifest>
\ No newline at end of file
diff --git a/core/core-i18n/src/main/androidx/core/androidx-core-core-i18n-documentation.md b/core/core-i18n/src/main/androidx/core/androidx-core-core-i18n-documentation.md
new file mode 100644
index 0000000..10494dc
--- /dev/null
+++ b/core/core-i18n/src/main/androidx/core/androidx-core-core-i18n-documentation.md
@@ -0,0 +1,8 @@
+# Module root
+
+Core Internationalization
+
+# Package androidx.core.i18n
+
+This library provides functionality for good internationalization (messages, plurals, date / time formatting).
+
diff --git a/core/core/api/current.txt b/core/core/api/current.txt
index abd8918..4bed9ff 100644
--- a/core/core/api/current.txt
+++ b/core/core/api/current.txt
@@ -840,17 +840,17 @@
}
public final class RemoteInput {
- method public static void addDataResultToIntent(androidx.core.app.RemoteInput!, android.content.Intent!, java.util.Map<java.lang.String!,android.net.Uri!>!);
- method public static void addResultsToIntent(androidx.core.app.RemoteInput![]!, android.content.Intent!, android.os.Bundle!);
+ method public static void addDataResultToIntent(androidx.core.app.RemoteInput, android.content.Intent, java.util.Map<java.lang.String!,android.net.Uri!>);
+ method public static void addResultsToIntent(androidx.core.app.RemoteInput![], android.content.Intent, android.os.Bundle);
method public boolean getAllowFreeFormInput();
- method public java.util.Set<java.lang.String!>! getAllowedDataTypes();
- method public CharSequence![]! getChoices();
- method public static java.util.Map<java.lang.String!,android.net.Uri!>! getDataResultsFromIntent(android.content.Intent!, String!);
+ method public java.util.Set<java.lang.String!>? getAllowedDataTypes();
+ method public CharSequence![]? getChoices();
+ method public static java.util.Map<java.lang.String!,android.net.Uri!>? getDataResultsFromIntent(android.content.Intent, String);
method public int getEditChoicesBeforeSending();
- method public android.os.Bundle! getExtras();
- method public CharSequence! getLabel();
- method public String! getResultKey();
- method public static android.os.Bundle! getResultsFromIntent(android.content.Intent!);
+ method public android.os.Bundle getExtras();
+ method public CharSequence? getLabel();
+ method public String getResultKey();
+ method public static android.os.Bundle? getResultsFromIntent(android.content.Intent);
method public static int getResultsSource(android.content.Intent);
method public boolean isDataOnly();
method public static void setResultsSource(android.content.Intent, int);
@@ -962,7 +962,7 @@
method public androidx.core.app.TaskStackBuilder addNextIntentWithParentStack(android.content.Intent);
method public androidx.core.app.TaskStackBuilder addParentStack(android.app.Activity);
method public androidx.core.app.TaskStackBuilder addParentStack(Class<?>);
- method public androidx.core.app.TaskStackBuilder! addParentStack(android.content.ComponentName!);
+ method public androidx.core.app.TaskStackBuilder addParentStack(android.content.ComponentName);
method public static androidx.core.app.TaskStackBuilder create(android.content.Context);
method public android.content.Intent? editIntentAt(int);
method @Deprecated public static androidx.core.app.TaskStackBuilder! from(android.content.Context!);
@@ -971,7 +971,7 @@
method public android.content.Intent![] getIntents();
method public android.app.PendingIntent? getPendingIntent(int, int);
method public android.app.PendingIntent? getPendingIntent(int, int, android.os.Bundle?);
- method @Deprecated public java.util.Iterator<android.content.Intent!>! iterator();
+ method @Deprecated public java.util.Iterator<android.content.Intent!> iterator();
method public void startActivities();
method public void startActivities(android.os.Bundle?);
}
diff --git a/core/core/api/public_plus_experimental_current.txt b/core/core/api/public_plus_experimental_current.txt
index 832cbba..8554f3b 100644
--- a/core/core/api/public_plus_experimental_current.txt
+++ b/core/core/api/public_plus_experimental_current.txt
@@ -840,17 +840,17 @@
}
public final class RemoteInput {
- method public static void addDataResultToIntent(androidx.core.app.RemoteInput!, android.content.Intent!, java.util.Map<java.lang.String!,android.net.Uri!>!);
- method public static void addResultsToIntent(androidx.core.app.RemoteInput![]!, android.content.Intent!, android.os.Bundle!);
+ method public static void addDataResultToIntent(androidx.core.app.RemoteInput, android.content.Intent, java.util.Map<java.lang.String!,android.net.Uri!>);
+ method public static void addResultsToIntent(androidx.core.app.RemoteInput![], android.content.Intent, android.os.Bundle);
method public boolean getAllowFreeFormInput();
- method public java.util.Set<java.lang.String!>! getAllowedDataTypes();
- method public CharSequence![]! getChoices();
- method public static java.util.Map<java.lang.String!,android.net.Uri!>! getDataResultsFromIntent(android.content.Intent!, String!);
+ method public java.util.Set<java.lang.String!>? getAllowedDataTypes();
+ method public CharSequence![]? getChoices();
+ method public static java.util.Map<java.lang.String!,android.net.Uri!>? getDataResultsFromIntent(android.content.Intent, String);
method public int getEditChoicesBeforeSending();
- method public android.os.Bundle! getExtras();
- method public CharSequence! getLabel();
- method public String! getResultKey();
- method public static android.os.Bundle! getResultsFromIntent(android.content.Intent!);
+ method public android.os.Bundle getExtras();
+ method public CharSequence? getLabel();
+ method public String getResultKey();
+ method public static android.os.Bundle? getResultsFromIntent(android.content.Intent);
method public static int getResultsSource(android.content.Intent);
method public boolean isDataOnly();
method public static void setResultsSource(android.content.Intent, int);
@@ -962,7 +962,7 @@
method public androidx.core.app.TaskStackBuilder addNextIntentWithParentStack(android.content.Intent);
method public androidx.core.app.TaskStackBuilder addParentStack(android.app.Activity);
method public androidx.core.app.TaskStackBuilder addParentStack(Class<?>);
- method public androidx.core.app.TaskStackBuilder! addParentStack(android.content.ComponentName!);
+ method public androidx.core.app.TaskStackBuilder addParentStack(android.content.ComponentName);
method public static androidx.core.app.TaskStackBuilder create(android.content.Context);
method public android.content.Intent? editIntentAt(int);
method @Deprecated public static androidx.core.app.TaskStackBuilder! from(android.content.Context!);
@@ -971,7 +971,7 @@
method public android.content.Intent![] getIntents();
method public android.app.PendingIntent? getPendingIntent(int, int);
method public android.app.PendingIntent? getPendingIntent(int, int, android.os.Bundle?);
- method @Deprecated public java.util.Iterator<android.content.Intent!>! iterator();
+ method @Deprecated public java.util.Iterator<android.content.Intent!> iterator();
method public void startActivities();
method public void startActivities(android.os.Bundle?);
}
diff --git a/core/core/api/restricted_current.txt b/core/core/api/restricted_current.txt
index ce1b4a2..d008d07 100644
--- a/core/core/api/restricted_current.txt
+++ b/core/core/api/restricted_current.txt
@@ -930,26 +930,26 @@
method public void setShouldShowIcon(boolean);
method public boolean shouldShowIcon();
method @RequiresApi(26) public android.app.RemoteAction toRemoteAction();
- field @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP) @androidx.versionedparcelable.ParcelField(4) public android.app.PendingIntent! mActionIntent;
- field @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP) @androidx.versionedparcelable.ParcelField(3) public CharSequence! mContentDescription;
+ field @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP) @androidx.versionedparcelable.ParcelField(4) public android.app.PendingIntent mActionIntent;
+ field @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP) @androidx.versionedparcelable.ParcelField(3) public CharSequence mContentDescription;
field @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP) @androidx.versionedparcelable.ParcelField(5) public boolean mEnabled;
- field @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP) @androidx.versionedparcelable.ParcelField(1) public androidx.core.graphics.drawable.IconCompat! mIcon;
+ field @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP) @androidx.versionedparcelable.ParcelField(1) public androidx.core.graphics.drawable.IconCompat mIcon;
field @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP) @androidx.versionedparcelable.ParcelField(6) public boolean mShouldShowIcon;
- field @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP) @androidx.versionedparcelable.ParcelField(2) public CharSequence! mTitle;
+ field @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP) @androidx.versionedparcelable.ParcelField(2) public CharSequence mTitle;
}
public final class RemoteInput {
- method public static void addDataResultToIntent(androidx.core.app.RemoteInput!, android.content.Intent!, java.util.Map<java.lang.String!,android.net.Uri!>!);
- method public static void addResultsToIntent(androidx.core.app.RemoteInput![]!, android.content.Intent!, android.os.Bundle!);
+ method public static void addDataResultToIntent(androidx.core.app.RemoteInput, android.content.Intent, java.util.Map<java.lang.String!,android.net.Uri!>);
+ method public static void addResultsToIntent(androidx.core.app.RemoteInput![], android.content.Intent, android.os.Bundle);
method public boolean getAllowFreeFormInput();
- method public java.util.Set<java.lang.String!>! getAllowedDataTypes();
- method public CharSequence![]! getChoices();
- method public static java.util.Map<java.lang.String!,android.net.Uri!>! getDataResultsFromIntent(android.content.Intent!, String!);
+ method public java.util.Set<java.lang.String!>? getAllowedDataTypes();
+ method public CharSequence![]? getChoices();
+ method public static java.util.Map<java.lang.String!,android.net.Uri!>? getDataResultsFromIntent(android.content.Intent, String);
method @androidx.core.app.RemoteInput.EditChoicesBeforeSending public int getEditChoicesBeforeSending();
- method public android.os.Bundle! getExtras();
- method public CharSequence! getLabel();
- method public String! getResultKey();
- method public static android.os.Bundle! getResultsFromIntent(android.content.Intent!);
+ method public android.os.Bundle getExtras();
+ method public CharSequence? getLabel();
+ method public String getResultKey();
+ method public static android.os.Bundle? getResultsFromIntent(android.content.Intent);
method @androidx.core.app.RemoteInput.Source public static int getResultsSource(android.content.Intent);
method public boolean isDataOnly();
method public static void setResultsSource(android.content.Intent, @androidx.core.app.RemoteInput.Source int);
@@ -1070,7 +1070,7 @@
method public androidx.core.app.TaskStackBuilder addNextIntentWithParentStack(android.content.Intent);
method public androidx.core.app.TaskStackBuilder addParentStack(android.app.Activity);
method public androidx.core.app.TaskStackBuilder addParentStack(Class<?>);
- method public androidx.core.app.TaskStackBuilder! addParentStack(android.content.ComponentName!);
+ method public androidx.core.app.TaskStackBuilder addParentStack(android.content.ComponentName);
method public static androidx.core.app.TaskStackBuilder create(android.content.Context);
method public android.content.Intent? editIntentAt(int);
method @Deprecated public static androidx.core.app.TaskStackBuilder! from(android.content.Context!);
@@ -1079,7 +1079,7 @@
method public android.content.Intent![] getIntents();
method public android.app.PendingIntent? getPendingIntent(int, int);
method public android.app.PendingIntent? getPendingIntent(int, int, android.os.Bundle?);
- method @Deprecated public java.util.Iterator<android.content.Intent!>! iterator();
+ method @Deprecated public java.util.Iterator<android.content.Intent!> iterator();
method public void startActivities();
method public void startActivities(android.os.Bundle?);
}
diff --git a/core/core/lint-baseline.xml b/core/core/lint-baseline.xml
index 9a02222..0864e15 100644
--- a/core/core/lint-baseline.xml
+++ b/core/core/lint-baseline.xml
@@ -1225,50 +1225,6 @@
<issue
id="BanUncheckedReflection"
message="Calling `Method.invoke` without an SDK check"
- errorLine1=" return (int) icon.getClass().getMethod("getType").invoke(icon);"
- errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
- <location
- file="src/main/java/androidx/core/graphics/drawable/IconCompat.java"
- line="1104"
- column="26"/>
- </issue>
-
- <issue
- id="BanUncheckedReflection"
- message="Calling `Method.invoke` without an SDK check"
- errorLine1=" return (String) icon.getClass().getMethod("getResPackage").invoke(icon);"
- errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
- <location
- file="src/main/java/androidx/core/graphics/drawable/IconCompat.java"
- line="1132"
- column="29"/>
- </issue>
-
- <issue
- id="BanUncheckedReflection"
- message="Calling `Method.invoke` without an SDK check"
- errorLine1=" return (int) icon.getClass().getMethod("getResId").invoke(icon);"
- errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
- <location
- file="src/main/java/androidx/core/graphics/drawable/IconCompat.java"
- line="1161"
- column="26"/>
- </issue>
-
- <issue
- id="BanUncheckedReflection"
- message="Calling `Method.invoke` without an SDK check"
- errorLine1=" return (Uri) icon.getClass().getMethod("getUri").invoke(icon);"
- errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
- <location
- file="src/main/java/androidx/core/graphics/drawable/IconCompat.java"
- line="1189"
- column="26"/>
- </issue>
-
- <issue
- id="BanUncheckedReflection"
- message="Calling `Method.invoke` without an SDK check"
errorLine1=" Object value = sActionBarOnMenuKeyMethod.invoke(actionBar, event);"
errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
<location
@@ -6823,292 +6779,6 @@
<issue
id="ClassVerificationFailure"
- message="This call references a method added in API level 21; however, the containing class androidx.core.app.Person is reachable from earlier API levels and will fail run-time class verification."
- errorLine1=" .setName(bundle.getString(NAME_KEY))"
- errorLine2=" ~~~~~~~~~">
- <location
- file="src/main/java/androidx/core/app/Person.java"
- line="71"
- column="33"/>
- </issue>
-
- <issue
- id="ClassVerificationFailure"
- message="This call references a method added in API level 21; however, the containing class androidx.core.app.Person is reachable from earlier API levels and will fail run-time class verification."
- errorLine1=" .setUri(bundle.getString(URI_KEY))"
- errorLine2=" ~~~~~~~~~">
- <location
- file="src/main/java/androidx/core/app/Person.java"
- line="72"
- column="32"/>
- </issue>
-
- <issue
- id="ClassVerificationFailure"
- message="This call references a method added in API level 21; however, the containing class androidx.core.app.Person is reachable from earlier API levels and will fail run-time class verification."
- errorLine1=" .setKey(bundle.getString(KEY_KEY))"
- errorLine2=" ~~~~~~~~~">
- <location
- file="src/main/java/androidx/core/app/Person.java"
- line="73"
- column="32"/>
- </issue>
-
- <issue
- id="ClassVerificationFailure"
- message="This call references a method added in API level 22; however, the containing class androidx.core.app.Person is reachable from earlier API levels and will fail run-time class verification."
- errorLine1=" .setBot(bundle.getBoolean(IS_BOT_KEY))"
- errorLine2=" ~~~~~~~~~~">
- <location
- file="src/main/java/androidx/core/app/Person.java"
- line="74"
- column="32"/>
- </issue>
-
- <issue
- id="ClassVerificationFailure"
- message="This call references a method added in API level 22; however, the containing class androidx.core.app.Person is reachable from earlier API levels and will fail run-time class verification."
- errorLine1=" .setImportant(bundle.getBoolean(IS_IMPORTANT_KEY))"
- errorLine2=" ~~~~~~~~~~">
- <location
- file="src/main/java/androidx/core/app/Person.java"
- line="75"
- column="38"/>
- </issue>
-
- <issue
- id="ClassVerificationFailure"
- message="This call references a method added in API level 28; however, the containing class androidx.core.app.Person is reachable from earlier API levels and will fail run-time class verification."
- errorLine1=" .setName(person.getName())"
- errorLine2=" ~~~~~~~">
- <location
- file="src/main/java/androidx/core/app/Person.java"
- line="89"
- column="33"/>
- </issue>
-
- <issue
- id="ClassVerificationFailure"
- message="This call references a method added in API level 28; however, the containing class androidx.core.app.Person is reachable from earlier API levels and will fail run-time class verification."
- errorLine1=" (person.getIcon() != null)"
- errorLine2=" ~~~~~~~">
- <location
- file="src/main/java/androidx/core/app/Person.java"
- line="91"
- column="33"/>
- </issue>
-
- <issue
- id="ClassVerificationFailure"
- message="This call references a method added in API level 28; however, the containing class androidx.core.app.Person is reachable from earlier API levels and will fail run-time class verification."
- errorLine1=" ? IconCompat.createFromIcon(person.getIcon())"
- errorLine2=" ~~~~~~~">
- <location
- file="src/main/java/androidx/core/app/Person.java"
- line="92"
- column="68"/>
- </issue>
-
- <issue
- id="ClassVerificationFailure"
- message="This call references a method added in API level 28; however, the containing class androidx.core.app.Person is reachable from earlier API levels and will fail run-time class verification."
- errorLine1=" .setUri(person.getUri())"
- errorLine2=" ~~~~~~">
- <location
- file="src/main/java/androidx/core/app/Person.java"
- line="94"
- column="32"/>
- </issue>
-
- <issue
- id="ClassVerificationFailure"
- message="This call references a method added in API level 28; however, the containing class androidx.core.app.Person is reachable from earlier API levels and will fail run-time class verification."
- errorLine1=" .setKey(person.getKey())"
- errorLine2=" ~~~~~~">
- <location
- file="src/main/java/androidx/core/app/Person.java"
- line="95"
- column="32"/>
- </issue>
-
- <issue
- id="ClassVerificationFailure"
- message="This call references a method added in API level 28; however, the containing class androidx.core.app.Person is reachable from earlier API levels and will fail run-time class verification."
- errorLine1=" .setBot(person.isBot())"
- errorLine2=" ~~~~~">
- <location
- file="src/main/java/androidx/core/app/Person.java"
- line="96"
- column="32"/>
- </issue>
-
- <issue
- id="ClassVerificationFailure"
- message="This call references a method added in API level 28; however, the containing class androidx.core.app.Person is reachable from earlier API levels and will fail run-time class verification."
- errorLine1=" .setImportant(person.isImportant())"
- errorLine2=" ~~~~~~~~~~~">
- <location
- file="src/main/java/androidx/core/app/Person.java"
- line="97"
- column="38"/>
- </issue>
-
- <issue
- id="ClassVerificationFailure"
- message="This call references a method added in API level 21; however, the containing class androidx.core.app.Person is reachable from earlier API levels and will fail run-time class verification."
- errorLine1=" PersistableBundle result = new PersistableBundle();"
- errorLine2=" ~~~~~~~~~~~~~~~~~~~~~">
- <location
- file="src/main/java/androidx/core/app/Person.java"
- line="151"
- column="36"/>
- </issue>
-
- <issue
- id="ClassVerificationFailure"
- message="This call references a method added in API level 21; however, the containing class androidx.core.app.Person is reachable from earlier API levels and will fail run-time class verification."
- errorLine1=" result.putString(NAME_KEY, mName != null ? mName.toString() : null);"
- errorLine2=" ~~~~~~~~~">
- <location
- file="src/main/java/androidx/core/app/Person.java"
- line="152"
- column="16"/>
- </issue>
-
- <issue
- id="ClassVerificationFailure"
- message="This call references a method added in API level 21; however, the containing class androidx.core.app.Person is reachable from earlier API levels and will fail run-time class verification."
- errorLine1=" result.putString(URI_KEY, mUri);"
- errorLine2=" ~~~~~~~~~">
- <location
- file="src/main/java/androidx/core/app/Person.java"
- line="153"
- column="16"/>
- </issue>
-
- <issue
- id="ClassVerificationFailure"
- message="This call references a method added in API level 21; however, the containing class androidx.core.app.Person is reachable from earlier API levels and will fail run-time class verification."
- errorLine1=" result.putString(KEY_KEY, mKey);"
- errorLine2=" ~~~~~~~~~">
- <location
- file="src/main/java/androidx/core/app/Person.java"
- line="154"
- column="16"/>
- </issue>
-
- <issue
- id="ClassVerificationFailure"
- message="This call references a method added in API level 22; however, the containing class androidx.core.app.Person is reachable from earlier API levels and will fail run-time class verification."
- errorLine1=" result.putBoolean(IS_BOT_KEY, mIsBot);"
- errorLine2=" ~~~~~~~~~~">
- <location
- file="src/main/java/androidx/core/app/Person.java"
- line="155"
- column="16"/>
- </issue>
-
- <issue
- id="ClassVerificationFailure"
- message="This call references a method added in API level 22; however, the containing class androidx.core.app.Person is reachable from earlier API levels and will fail run-time class verification."
- errorLine1=" result.putBoolean(IS_IMPORTANT_KEY, mIsImportant);"
- errorLine2=" ~~~~~~~~~~">
- <location
- file="src/main/java/androidx/core/app/Person.java"
- line="156"
- column="16"/>
- </issue>
-
- <issue
- id="ClassVerificationFailure"
- message="This call references a method added in API level 28; however, the containing class androidx.core.app.Person is reachable from earlier API levels and will fail run-time class verification."
- errorLine1=" return new android.app.Person.Builder()"
- errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
- <location
- file="src/main/java/androidx/core/app/Person.java"
- line="175"
- column="16"/>
- </issue>
-
- <issue
- id="ClassVerificationFailure"
- message="This call references a method added in API level 28; however, the containing class androidx.core.app.Person is reachable from earlier API levels and will fail run-time class verification."
- errorLine1=" .setName(getName())"
- errorLine2=" ~~~~~~~">
- <location
- file="src/main/java/androidx/core/app/Person.java"
- line="176"
- column="18"/>
- </issue>
-
- <issue
- id="ClassVerificationFailure"
- message="This call references a method added in API level 28; however, the containing class androidx.core.app.Person is reachable from earlier API levels and will fail run-time class verification."
- errorLine1=" .setIcon((getIcon() != null) ? getIcon().toIcon() : null)"
- errorLine2=" ~~~~~~~">
- <location
- file="src/main/java/androidx/core/app/Person.java"
- line="177"
- column="18"/>
- </issue>
-
- <issue
- id="ClassVerificationFailure"
- message="This call references a method added in API level 28; however, the containing class androidx.core.app.Person is reachable from earlier API levels and will fail run-time class verification."
- errorLine1=" .setUri(getUri())"
- errorLine2=" ~~~~~~">
- <location
- file="src/main/java/androidx/core/app/Person.java"
- line="178"
- column="18"/>
- </issue>
-
- <issue
- id="ClassVerificationFailure"
- message="This call references a method added in API level 28; however, the containing class androidx.core.app.Person is reachable from earlier API levels and will fail run-time class verification."
- errorLine1=" .setKey(getKey())"
- errorLine2=" ~~~~~~">
- <location
- file="src/main/java/androidx/core/app/Person.java"
- line="179"
- column="18"/>
- </issue>
-
- <issue
- id="ClassVerificationFailure"
- message="This call references a method added in API level 28; however, the containing class androidx.core.app.Person is reachable from earlier API levels and will fail run-time class verification."
- errorLine1=" .setBot(isBot())"
- errorLine2=" ~~~~~~">
- <location
- file="src/main/java/androidx/core/app/Person.java"
- line="180"
- column="18"/>
- </issue>
-
- <issue
- id="ClassVerificationFailure"
- message="This call references a method added in API level 28; however, the containing class androidx.core.app.Person is reachable from earlier API levels and will fail run-time class verification."
- errorLine1=" .setImportant(isImportant())"
- errorLine2=" ~~~~~~~~~~~~">
- <location
- file="src/main/java/androidx/core/app/Person.java"
- line="181"
- column="18"/>
- </issue>
-
- <issue
- id="ClassVerificationFailure"
- message="This call references a method added in API level 28; however, the containing class androidx.core.app.Person is reachable from earlier API levels and will fail run-time class verification."
- errorLine1=" .build();"
- errorLine2=" ~~~~~">
- <location
- file="src/main/java/androidx/core/app/Person.java"
- line="182"
- column="18"/>
- </issue>
-
- <issue
- id="ClassVerificationFailure"
message="This call references a method added in API level 28; however, the containing class androidx.core.text.PrecomputedTextCompat.Params is reachable from earlier API levels and will fail run-time class verification."
errorLine1=" mWrapped = new PrecomputedText.Params.Builder(paint)"
errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
@@ -7538,391 +7208,6 @@
<issue
id="ClassVerificationFailure"
- message="This call references a method added in API level 26; however, the containing class androidx.core.app.RemoteActionCompat is reachable from earlier API levels and will fail run-time class verification."
- errorLine1=" IconCompat.createFromIcon(remoteAction.getIcon()), remoteAction.getTitle(),"
- errorLine2=" ~~~~~~~">
- <location
- file="src/main/java/androidx/core/app/RemoteActionCompat.java"
- line="117"
- column="56"/>
- </issue>
-
- <issue
- id="ClassVerificationFailure"
- message="This call references a method added in API level 26; however, the containing class androidx.core.app.RemoteActionCompat is reachable from earlier API levels and will fail run-time class verification."
- errorLine1=" IconCompat.createFromIcon(remoteAction.getIcon()), remoteAction.getTitle(),"
- errorLine2=" ~~~~~~~~">
- <location
- file="src/main/java/androidx/core/app/RemoteActionCompat.java"
- line="117"
- column="81"/>
- </issue>
-
- <issue
- id="ClassVerificationFailure"
- message="This call references a method added in API level 26; however, the containing class androidx.core.app.RemoteActionCompat is reachable from earlier API levels and will fail run-time class verification."
- errorLine1=" remoteAction.getContentDescription(), remoteAction.getActionIntent());"
- errorLine2=" ~~~~~~~~~~~~~~~~~~~~~">
- <location
- file="src/main/java/androidx/core/app/RemoteActionCompat.java"
- line="118"
- column="30"/>
- </issue>
-
- <issue
- id="ClassVerificationFailure"
- message="This call references a method added in API level 26; however, the containing class androidx.core.app.RemoteActionCompat is reachable from earlier API levels and will fail run-time class verification."
- errorLine1=" remoteAction.getContentDescription(), remoteAction.getActionIntent());"
- errorLine2=" ~~~~~~~~~~~~~~~">
- <location
- file="src/main/java/androidx/core/app/RemoteActionCompat.java"
- line="118"
- column="68"/>
- </issue>
-
- <issue
- id="ClassVerificationFailure"
- message="This call references a method added in API level 26; however, the containing class androidx.core.app.RemoteActionCompat is reachable from earlier API levels and will fail run-time class verification."
- errorLine1=" action.setEnabled(remoteAction.isEnabled());"
- errorLine2=" ~~~~~~~~~">
- <location
- file="src/main/java/androidx/core/app/RemoteActionCompat.java"
- line="119"
- column="40"/>
- </issue>
-
- <issue
- id="ClassVerificationFailure"
- message="This call references a method added in API level 28; however, the containing class androidx.core.app.RemoteActionCompat is reachable from earlier API levels and will fail run-time class verification."
- errorLine1=" action.setShouldShowIcon(remoteAction.shouldShowIcon());"
- errorLine2=" ~~~~~~~~~~~~~~">
- <location
- file="src/main/java/androidx/core/app/RemoteActionCompat.java"
- line="121"
- column="51"/>
- </issue>
-
- <issue
- id="ClassVerificationFailure"
- message="This call references a method added in API level 26; however, the containing class androidx.core.app.RemoteActionCompat is reachable from earlier API levels and will fail run-time class verification."
- errorLine1=" RemoteAction action = new RemoteAction(mIcon.toIcon(), mTitle, mContentDescription,"
- errorLine2=" ~~~~~~~~~~~~~~~~">
- <location
- file="src/main/java/androidx/core/app/RemoteActionCompat.java"
- line="190"
- column="31"/>
- </issue>
-
- <issue
- id="ClassVerificationFailure"
- message="This call references a method added in API level 26; however, the containing class androidx.core.app.RemoteActionCompat is reachable from earlier API levels and will fail run-time class verification."
- errorLine1=" action.setEnabled(isEnabled());"
- errorLine2=" ~~~~~~~~~~">
- <location
- file="src/main/java/androidx/core/app/RemoteActionCompat.java"
- line="192"
- column="16"/>
- </issue>
-
- <issue
- id="ClassVerificationFailure"
- message="This call references a method added in API level 28; however, the containing class androidx.core.app.RemoteActionCompat is reachable from earlier API levels and will fail run-time class verification."
- errorLine1=" action.setShouldShowIcon(shouldShowIcon());"
- errorLine2=" ~~~~~~~~~~~~~~~~~">
- <location
- file="src/main/java/androidx/core/app/RemoteActionCompat.java"
- line="194"
- column="20"/>
- </issue>
-
- <issue
- id="ClassVerificationFailure"
- message="This call references a method added in API level 26; however, the containing class androidx.core.app.RemoteInput is reachable from earlier API levels and will fail run-time class verification."
- errorLine1=" return android.app.RemoteInput.getDataResultsFromIntent(intent, remoteInputResultKey);"
- errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~">
- <location
- file="src/main/java/androidx/core/app/RemoteInput.java"
- line="339"
- column="44"/>
- </issue>
-
- <issue
- id="ClassVerificationFailure"
- message="This call references a method added in API level 20; however, the containing class androidx.core.app.RemoteInput is reachable from earlier API levels and will fail run-time class verification."
- errorLine1=" return android.app.RemoteInput.getResultsFromIntent(intent);"
- errorLine2=" ~~~~~~~~~~~~~~~~~~~~">
- <location
- file="src/main/java/androidx/core/app/RemoteInput.java"
- line="377"
- column="44"/>
- </issue>
-
- <issue
- id="ClassVerificationFailure"
- message="This call references a method added in API level 20; however, the containing class androidx.core.app.RemoteInput is reachable from earlier API levels and will fail run-time class verification."
- errorLine1=" android.app.RemoteInput.addResultsToIntent(fromCompat(remoteInputs), intent, results);"
- errorLine2=" ~~~~~~~~~~~~~~~~~~">
- <location
- file="src/main/java/androidx/core/app/RemoteInput.java"
- line="403"
- column="37"/>
- </issue>
-
- <issue
- id="ClassVerificationFailure"
- message="This call references a method added in API level 20; however, the containing class androidx.core.app.RemoteInput is reachable from earlier API levels and will fail run-time class verification."
- errorLine1=" android.app.RemoteInput.addResultsToIntent("
- errorLine2=" ~~~~~~~~~~~~~~~~~~">
- <location
- file="src/main/java/androidx/core/app/RemoteInput.java"
- line="426"
- column="41"/>
- </issue>
-
- <issue
- id="ClassVerificationFailure"
- message="This call references a method added in API level 16; however, the containing class androidx.core.app.RemoteInput is reachable from earlier API levels and will fail run-time class verification."
- errorLine1=" intent.setClipData(ClipData.newIntent(RemoteInput.RESULTS_CLIP_LABEL, clipDataIntent));"
- errorLine2=" ~~~~~~~~~~~">
- <location
- file="src/main/java/androidx/core/app/RemoteInput.java"
- line="452"
- column="20"/>
- </issue>
-
- <issue
- id="ClassVerificationFailure"
- message="This call references a method added in API level 26; however, the containing class androidx.core.app.RemoteInput is reachable from earlier API levels and will fail run-time class verification."
- errorLine1=" android.app.RemoteInput.addDataResultToIntent(fromCompat(remoteInput), intent, results);"
- errorLine2=" ~~~~~~~~~~~~~~~~~~~~~">
- <location
- file="src/main/java/androidx/core/app/RemoteInput.java"
- line="467"
- column="37"/>
- </issue>
-
- <issue
- id="ClassVerificationFailure"
- message="This call references a method added in API level 16; however, the containing class androidx.core.app.RemoteInput is reachable from earlier API levels and will fail run-time class verification."
- errorLine1=" intent.setClipData(ClipData.newIntent(RemoteInput.RESULTS_CLIP_LABEL, clipDataIntent));"
- errorLine2=" ~~~~~~~~~~~">
- <location
- file="src/main/java/androidx/core/app/RemoteInput.java"
- line="487"
- column="20"/>
- </issue>
-
- <issue
- id="ClassVerificationFailure"
- message="This call references a method added in API level 28; however, the containing class androidx.core.app.RemoteInput is reachable from earlier API levels and will fail run-time class verification."
- errorLine1=" android.app.RemoteInput.setResultsSource(intent, source);"
- errorLine2=" ~~~~~~~~~~~~~~~~">
- <location
- file="src/main/java/androidx/core/app/RemoteInput.java"
- line="506"
- column="37"/>
- </issue>
-
- <issue
- id="ClassVerificationFailure"
- message="This call references a method added in API level 16; however, the containing class androidx.core.app.RemoteInput is reachable from earlier API levels and will fail run-time class verification."
- errorLine1=" intent.setClipData(ClipData.newIntent(RESULTS_CLIP_LABEL, clipDataIntent));"
- errorLine2=" ~~~~~~~~~~~">
- <location
- file="src/main/java/androidx/core/app/RemoteInput.java"
- line="513"
- column="20"/>
- </issue>
-
- <issue
- id="ClassVerificationFailure"
- message="This call references a method added in API level 28; however, the containing class androidx.core.app.RemoteInput is reachable from earlier API levels and will fail run-time class verification."
- errorLine1=" return android.app.RemoteInput.getResultsSource(intent);"
- errorLine2=" ~~~~~~~~~~~~~~~~">
- <location
- file="src/main/java/androidx/core/app/RemoteInput.java"
- line="531"
- column="44"/>
- </issue>
-
- <issue
- id="ClassVerificationFailure"
- message="This call references a method added in API level 20; however, the containing class androidx.core.app.RemoteInput is reachable from earlier API levels and will fail run-time class verification."
- errorLine1=" new android.app.RemoteInput.Builder(src.getResultKey())"
- errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
- <location
- file="src/main/java/androidx/core/app/RemoteInput.java"
- line="562"
- column="17"/>
- </issue>
-
- <issue
- id="ClassVerificationFailure"
- message="This call references a method added in API level 20; however, the containing class androidx.core.app.RemoteInput is reachable from earlier API levels and will fail run-time class verification."
- errorLine1=" .setLabel(src.getLabel())"
- errorLine2=" ~~~~~~~~">
- <location
- file="src/main/java/androidx/core/app/RemoteInput.java"
- line="563"
- column="26"/>
- </issue>
-
- <issue
- id="ClassVerificationFailure"
- message="This call references a method added in API level 20; however, the containing class androidx.core.app.RemoteInput is reachable from earlier API levels and will fail run-time class verification."
- errorLine1=" .setChoices(src.getChoices())"
- errorLine2=" ~~~~~~~~~~">
- <location
- file="src/main/java/androidx/core/app/RemoteInput.java"
- line="564"
- column="26"/>
- </issue>
-
- <issue
- id="ClassVerificationFailure"
- message="This call references a method added in API level 20; however, the containing class androidx.core.app.RemoteInput is reachable from earlier API levels and will fail run-time class verification."
- errorLine1=" .setAllowFreeFormInput(src.getAllowFreeFormInput())"
- errorLine2=" ~~~~~~~~~~~~~~~~~~~~~">
- <location
- file="src/main/java/androidx/core/app/RemoteInput.java"
- line="565"
- column="26"/>
- </issue>
-
- <issue
- id="ClassVerificationFailure"
- message="This call references a method added in API level 20; however, the containing class androidx.core.app.RemoteInput is reachable from earlier API levels and will fail run-time class verification."
- errorLine1=" .addExtras(src.getExtras());"
- errorLine2=" ~~~~~~~~~">
- <location
- file="src/main/java/androidx/core/app/RemoteInput.java"
- line="566"
- column="26"/>
- </issue>
-
- <issue
- id="ClassVerificationFailure"
- message="This call references a method added in API level 26; however, the containing class androidx.core.app.RemoteInput is reachable from earlier API levels and will fail run-time class verification."
- errorLine1=" builder.setAllowDataType(allowedDataType, true);"
- errorLine2=" ~~~~~~~~~~~~~~~~">
- <location
- file="src/main/java/androidx/core/app/RemoteInput.java"
- line="571"
- column="29"/>
- </issue>
-
- <issue
- id="ClassVerificationFailure"
- message="This call references a method added in API level 29; however, the containing class androidx.core.app.RemoteInput is reachable from earlier API levels and will fail run-time class verification."
- errorLine1=" builder.setEditChoicesBeforeSending(src.getEditChoicesBeforeSending());"
- errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~">
- <location
- file="src/main/java/androidx/core/app/RemoteInput.java"
- line="576"
- column="21"/>
- </issue>
-
- <issue
- id="ClassVerificationFailure"
- message="This call references a method added in API level 20; however, the containing class androidx.core.app.RemoteInput is reachable from earlier API levels and will fail run-time class verification."
- errorLine1=" return builder.build();"
- errorLine2=" ~~~~~">
- <location
- file="src/main/java/androidx/core/app/RemoteInput.java"
- line="578"
- column="24"/>
- </issue>
-
- <issue
- id="ClassVerificationFailure"
- message="This call references a method added in API level 20; however, the containing class androidx.core.app.RemoteInput is reachable from earlier API levels and will fail run-time class verification."
- errorLine1=" new RemoteInput.Builder(src.getResultKey())"
- errorLine2=" ~~~~~~~~~~~~">
- <location
- file="src/main/java/androidx/core/app/RemoteInput.java"
- line="584"
- column="45"/>
- </issue>
-
- <issue
- id="ClassVerificationFailure"
- message="This call references a method added in API level 20; however, the containing class androidx.core.app.RemoteInput is reachable from earlier API levels and will fail run-time class verification."
- errorLine1=" .setLabel(src.getLabel())"
- errorLine2=" ~~~~~~~~">
- <location
- file="src/main/java/androidx/core/app/RemoteInput.java"
- line="585"
- column="39"/>
- </issue>
-
- <issue
- id="ClassVerificationFailure"
- message="This call references a method added in API level 20; however, the containing class androidx.core.app.RemoteInput is reachable from earlier API levels and will fail run-time class verification."
- errorLine1=" .setChoices(src.getChoices())"
- errorLine2=" ~~~~~~~~~~">
- <location
- file="src/main/java/androidx/core/app/RemoteInput.java"
- line="586"
- column="41"/>
- </issue>
-
- <issue
- id="ClassVerificationFailure"
- message="This call references a method added in API level 20; however, the containing class androidx.core.app.RemoteInput is reachable from earlier API levels and will fail run-time class verification."
- errorLine1=" .setAllowFreeFormInput(src.getAllowFreeFormInput())"
- errorLine2=" ~~~~~~~~~~~~~~~~~~~~~">
- <location
- file="src/main/java/androidx/core/app/RemoteInput.java"
- line="587"
- column="52"/>
- </issue>
-
- <issue
- id="ClassVerificationFailure"
- message="This call references a method added in API level 20; however, the containing class androidx.core.app.RemoteInput is reachable from earlier API levels and will fail run-time class verification."
- errorLine1=" .addExtras(src.getExtras());"
- errorLine2=" ~~~~~~~~~">
- <location
- file="src/main/java/androidx/core/app/RemoteInput.java"
- line="588"
- column="40"/>
- </issue>
-
- <issue
- id="ClassVerificationFailure"
- message="This call references a method added in API level 26; however, the containing class androidx.core.app.RemoteInput is reachable from earlier API levels and will fail run-time class verification."
- errorLine1=" Set<String> allowedDataTypes = src.getAllowedDataTypes();"
- errorLine2=" ~~~~~~~~~~~~~~~~~~~">
- <location
- file="src/main/java/androidx/core/app/RemoteInput.java"
- line="590"
- column="48"/>
- </issue>
-
- <issue
- id="ClassVerificationFailure"
- message="This call references a method added in API level 29; however, the containing class androidx.core.app.RemoteInput is reachable from earlier API levels and will fail run-time class verification."
- errorLine1=" builder.setEditChoicesBeforeSending(src.getEditChoicesBeforeSending());"
- errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~">
- <location
- file="src/main/java/androidx/core/app/RemoteInput.java"
- line="598"
- column="53"/>
- </issue>
-
- <issue
- id="ClassVerificationFailure"
- message="This call references a method added in API level 16; however, the containing class androidx.core.app.RemoteInput is reachable from earlier API levels and will fail run-time class verification."
- errorLine1=" ClipData clipData = intent.getClipData();"
- errorLine2=" ~~~~~~~~~~~">
- <location
- file="src/main/java/androidx/core/app/RemoteInput.java"
- line="605"
- column="36"/>
- </issue>
-
- <issue
- id="ClassVerificationFailure"
message="This call references a method added in API level 28; however, the containing class androidx.core.database.sqlite.SQLiteCursorCompat is reachable from earlier API levels and will fail run-time class verification."
errorLine1=" cursor.setFillWindowForwardOnly(fillWindowForwardOnly);"
errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~">
@@ -7934,17 +7219,6 @@
<issue
id="ClassVerificationFailure"
- message="This call references a method added in API level 24; however, the containing class androidx.core.app.ServiceCompat is reachable from earlier API levels and will fail run-time class verification."
- errorLine1=" service.stopForeground(flags);"
- errorLine2=" ~~~~~~~~~~~~~~">
- <location
- file="src/main/java/androidx/core/app/ServiceCompat.java"
- line="100"
- column="21"/>
- </issue>
-
- <issue
- id="ClassVerificationFailure"
message="This call references a method added in API level 16; however, the containing class androidx.core.app.ShareCompat.IntentReader is reachable from earlier API levels and will fail run-time class verification."
errorLine1=" result = Html.escapeHtml(text);"
errorLine2=" ~~~~~~~~~~">
@@ -9100,17 +8374,6 @@
<issue
id="ClassVerificationFailure"
- message="This call references a method added in API level 16; however, the containing class androidx.core.app.TaskStackBuilder is reachable from earlier API levels and will fail run-time class verification."
- errorLine1=" return PendingIntent.getActivities(mSourceContext, requestCode, intents, flags,"
- errorLine2=" ~~~~~~~~~~~~~">
- <location
- file="src/main/java/androidx/core/app/TaskStackBuilder.java"
- line="341"
- column="34"/>
- </issue>
-
- <issue
- id="ClassVerificationFailure"
message="This call references a method added in API level 23; however, the containing class androidx.core.widget.TextViewCompat is reachable from earlier API levels and will fail run-time class verification."
errorLine1=" textView.setTextAppearance(resId);"
errorLine2=" ~~~~~~~~~~~~~~~~~">
@@ -9366,17 +8629,6 @@
<issue
id="KotlinPropertyAccess"
- message="This method should be called `getShouldShowIcon` such that `shouldShowIcon` can be accessed as a property from Kotlin; see https://android.github.io/kotlin-guides/interop.html#property-prefixes"
- errorLine1=" public boolean shouldShowIcon() {"
- errorLine2=" ~~~~~~~~~~~~~~">
- <location
- file="src/main/java/androidx/core/app/RemoteActionCompat.java"
- line="150"
- column="20"/>
- </issue>
-
- <issue
- id="KotlinPropertyAccess"
message="This method should be called `getMipMap` such that `mipMap` can be accessed as a property from Kotlin; see https://android.github.io/kotlin-guides/interop.html#property-prefixes"
errorLine1=" public boolean hasMipMap() {"
errorLine2=" ~~~~~~~~~">
@@ -10896,281 +10148,6 @@
<issue
id="UnknownNullness"
message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://android.github.io/kotlin-guides/interop.html#nullability-annotations"
- errorLine1=" public byte[] mData = null;"
- errorLine2=" ~~~~~~">
- <location
- file="src/main/java/androidx/core/graphics/drawable/IconCompat.java"
- line="176"
- column="12"/>
- </issue>
-
- <issue
- id="UnknownNullness"
- message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://android.github.io/kotlin-guides/interop.html#nullability-annotations"
- errorLine1=" public Parcelable mParcelable = null;"
- errorLine2=" ~~~~~~~~~~">
- <location
- file="src/main/java/androidx/core/graphics/drawable/IconCompat.java"
- line="182"
- column="12"/>
- </issue>
-
- <issue
- id="UnknownNullness"
- message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://android.github.io/kotlin-guides/interop.html#nullability-annotations"
- errorLine1=" public ColorStateList mTintList = null;"
- errorLine2=" ~~~~~~~~~~~~~~">
- <location
- file="src/main/java/androidx/core/graphics/drawable/IconCompat.java"
- line="206"
- column="12"/>
- </issue>
-
- <issue
- id="UnknownNullness"
- message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://android.github.io/kotlin-guides/interop.html#nullability-annotations"
- errorLine1=" public String mTintModeStr = null;"
- errorLine2=" ~~~~~~">
- <location
- file="src/main/java/androidx/core/graphics/drawable/IconCompat.java"
- line="216"
- column="12"/>
- </issue>
-
- <issue
- id="UnknownNullness"
- message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://android.github.io/kotlin-guides/interop.html#nullability-annotations"
- errorLine1=" public String mString1;"
- errorLine2=" ~~~~~~">
- <location
- file="src/main/java/androidx/core/graphics/drawable/IconCompat.java"
- line="223"
- column="12"/>
- </issue>
-
- <issue
- id="UnknownNullness"
- message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://android.github.io/kotlin-guides/interop.html#nullability-annotations"
- errorLine1=" public static IconCompat createWithResource(Context context, @DrawableRes int resId) {"
- errorLine2=" ~~~~~~~~~~">
- <location
- file="src/main/java/androidx/core/graphics/drawable/IconCompat.java"
- line="232"
- column="19"/>
- </issue>
-
- <issue
- id="UnknownNullness"
- message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://android.github.io/kotlin-guides/interop.html#nullability-annotations"
- errorLine1=" public static IconCompat createWithResource(Context context, @DrawableRes int resId) {"
- errorLine2=" ~~~~~~~">
- <location
- file="src/main/java/androidx/core/graphics/drawable/IconCompat.java"
- line="232"
- column="49"/>
- </issue>
-
- <issue
- id="UnknownNullness"
- message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://android.github.io/kotlin-guides/interop.html#nullability-annotations"
- errorLine1=" public static IconCompat createWithResource(Resources r, String pkg, @DrawableRes int resId) {"
- errorLine2=" ~~~~~~~~~~">
- <location
- file="src/main/java/androidx/core/graphics/drawable/IconCompat.java"
- line="243"
- column="19"/>
- </issue>
-
- <issue
- id="UnknownNullness"
- message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://android.github.io/kotlin-guides/interop.html#nullability-annotations"
- errorLine1=" public static IconCompat createWithResource(Resources r, String pkg, @DrawableRes int resId) {"
- errorLine2=" ~~~~~~~~~">
- <location
- file="src/main/java/androidx/core/graphics/drawable/IconCompat.java"
- line="243"
- column="49"/>
- </issue>
-
- <issue
- id="UnknownNullness"
- message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://android.github.io/kotlin-guides/interop.html#nullability-annotations"
- errorLine1=" public static IconCompat createWithResource(Resources r, String pkg, @DrawableRes int resId) {"
- errorLine2=" ~~~~~~">
- <location
- file="src/main/java/androidx/core/graphics/drawable/IconCompat.java"
- line="243"
- column="62"/>
- </issue>
-
- <issue
- id="UnknownNullness"
- message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://android.github.io/kotlin-guides/interop.html#nullability-annotations"
- errorLine1=" public static IconCompat createWithBitmap(Bitmap bits) {"
- errorLine2=" ~~~~~~~~~~">
- <location
- file="src/main/java/androidx/core/graphics/drawable/IconCompat.java"
- line="270"
- column="19"/>
- </issue>
-
- <issue
- id="UnknownNullness"
- message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://android.github.io/kotlin-guides/interop.html#nullability-annotations"
- errorLine1=" public static IconCompat createWithBitmap(Bitmap bits) {"
- errorLine2=" ~~~~~~">
- <location
- file="src/main/java/androidx/core/graphics/drawable/IconCompat.java"
- line="270"
- column="47"/>
- </issue>
-
- <issue
- id="UnknownNullness"
- message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://android.github.io/kotlin-guides/interop.html#nullability-annotations"
- errorLine1=" public static IconCompat createWithAdaptiveBitmap(Bitmap bits) {"
- errorLine2=" ~~~~~~~~~~">
- <location
- file="src/main/java/androidx/core/graphics/drawable/IconCompat.java"
- line="285"
- column="19"/>
- </issue>
-
- <issue
- id="UnknownNullness"
- message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://android.github.io/kotlin-guides/interop.html#nullability-annotations"
- errorLine1=" public static IconCompat createWithAdaptiveBitmap(Bitmap bits) {"
- errorLine2=" ~~~~~~">
- <location
- file="src/main/java/androidx/core/graphics/drawable/IconCompat.java"
- line="285"
- column="55"/>
- </issue>
-
- <issue
- id="UnknownNullness"
- message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://android.github.io/kotlin-guides/interop.html#nullability-annotations"
- errorLine1=" public static IconCompat createWithData(byte[] data, int offset, int length) {"
- errorLine2=" ~~~~~~~~~~">
- <location
- file="src/main/java/androidx/core/graphics/drawable/IconCompat.java"
- line="303"
- column="19"/>
- </issue>
-
- <issue
- id="UnknownNullness"
- message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://android.github.io/kotlin-guides/interop.html#nullability-annotations"
- errorLine1=" public static IconCompat createWithData(byte[] data, int offset, int length) {"
- errorLine2=" ~~~~~~">
- <location
- file="src/main/java/androidx/core/graphics/drawable/IconCompat.java"
- line="303"
- column="45"/>
- </issue>
-
- <issue
- id="UnknownNullness"
- message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://android.github.io/kotlin-guides/interop.html#nullability-annotations"
- errorLine1=" public static IconCompat createWithContentUri(String uri) {"
- errorLine2=" ~~~~~~~~~~">
- <location
- file="src/main/java/androidx/core/graphics/drawable/IconCompat.java"
- line="320"
- column="19"/>
- </issue>
-
- <issue
- id="UnknownNullness"
- message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://android.github.io/kotlin-guides/interop.html#nullability-annotations"
- errorLine1=" public static IconCompat createWithContentUri(String uri) {"
- errorLine2=" ~~~~~~">
- <location
- file="src/main/java/androidx/core/graphics/drawable/IconCompat.java"
- line="320"
- column="51"/>
- </issue>
-
- <issue
- id="UnknownNullness"
- message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://android.github.io/kotlin-guides/interop.html#nullability-annotations"
- errorLine1=" public static IconCompat createWithContentUri(Uri uri) {"
- errorLine2=" ~~~~~~~~~~">
- <location
- file="src/main/java/androidx/core/graphics/drawable/IconCompat.java"
- line="335"
- column="19"/>
- </issue>
-
- <issue
- id="UnknownNullness"
- message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://android.github.io/kotlin-guides/interop.html#nullability-annotations"
- errorLine1=" public static IconCompat createWithContentUri(Uri uri) {"
- errorLine2=" ~~~">
- <location
- file="src/main/java/androidx/core/graphics/drawable/IconCompat.java"
- line="335"
- column="51"/>
- </issue>
-
- <issue
- id="UnknownNullness"
- message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://android.github.io/kotlin-guides/interop.html#nullability-annotations"
- errorLine1=" public IconCompat setTint(@ColorInt int tint) {"
- errorLine2=" ~~~~~~~~~~">
- <location
- file="src/main/java/androidx/core/graphics/drawable/IconCompat.java"
- line="496"
- column="12"/>
- </issue>
-
- <issue
- id="UnknownNullness"
- message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://android.github.io/kotlin-guides/interop.html#nullability-annotations"
- errorLine1=" public IconCompat setTintList(ColorStateList tintList) {"
- errorLine2=" ~~~~~~~~~~">
- <location
- file="src/main/java/androidx/core/graphics/drawable/IconCompat.java"
- line="506"
- column="12"/>
- </issue>
-
- <issue
- id="UnknownNullness"
- message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://android.github.io/kotlin-guides/interop.html#nullability-annotations"
- errorLine1=" public IconCompat setTintList(ColorStateList tintList) {"
- errorLine2=" ~~~~~~~~~~~~~~">
- <location
- file="src/main/java/androidx/core/graphics/drawable/IconCompat.java"
- line="506"
- column="35"/>
- </issue>
-
- <issue
- id="UnknownNullness"
- message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://android.github.io/kotlin-guides/interop.html#nullability-annotations"
- errorLine1=" public IconCompat setTintMode(PorterDuff.Mode mode) {"
- errorLine2=" ~~~~~~~~~~">
- <location
- file="src/main/java/androidx/core/graphics/drawable/IconCompat.java"
- line="517"
- column="12"/>
- </issue>
-
- <issue
- id="UnknownNullness"
- message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://android.github.io/kotlin-guides/interop.html#nullability-annotations"
- errorLine1=" public IconCompat setTintMode(PorterDuff.Mode mode) {"
- errorLine2=" ~~~~~~~~~~~~~~~">
- <location
- file="src/main/java/androidx/core/graphics/drawable/IconCompat.java"
- line="517"
- column="35"/>
- </issue>
-
- <issue
- id="UnknownNullness"
- message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://android.github.io/kotlin-guides/interop.html#nullability-annotations"
errorLine1=" public Locale get(int index) {"
errorLine2=" ~~~~~~">
<location
@@ -12095,226 +11072,6 @@
<issue
id="UnknownNullness"
message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://android.github.io/kotlin-guides/interop.html#nullability-annotations"
- errorLine1=" public IconCompat mIcon;"
- errorLine2=" ~~~~~~~~~~">
- <location
- file="src/main/java/androidx/core/app/RemoteActionCompat.java"
- line="47"
- column="12"/>
- </issue>
-
- <issue
- id="UnknownNullness"
- message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://android.github.io/kotlin-guides/interop.html#nullability-annotations"
- errorLine1=" public CharSequence mTitle;"
- errorLine2=" ~~~~~~~~~~~~">
- <location
- file="src/main/java/androidx/core/app/RemoteActionCompat.java"
- line="53"
- column="12"/>
- </issue>
-
- <issue
- id="UnknownNullness"
- message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://android.github.io/kotlin-guides/interop.html#nullability-annotations"
- errorLine1=" public CharSequence mContentDescription;"
- errorLine2=" ~~~~~~~~~~~~">
- <location
- file="src/main/java/androidx/core/app/RemoteActionCompat.java"
- line="59"
- column="12"/>
- </issue>
-
- <issue
- id="UnknownNullness"
- message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://android.github.io/kotlin-guides/interop.html#nullability-annotations"
- errorLine1=" public PendingIntent mActionIntent;"
- errorLine2=" ~~~~~~~~~~~~~">
- <location
- file="src/main/java/androidx/core/app/RemoteActionCompat.java"
- line="65"
- column="12"/>
- </issue>
-
- <issue
- id="UnknownNullness"
- message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://android.github.io/kotlin-guides/interop.html#nullability-annotations"
- errorLine1=" public String getResultKey() {"
- errorLine2=" ~~~~~~">
- <location
- file="src/main/java/androidx/core/app/RemoteInput.java"
- line="115"
- column="12"/>
- </issue>
-
- <issue
- id="UnknownNullness"
- message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://android.github.io/kotlin-guides/interop.html#nullability-annotations"
- errorLine1=" public CharSequence getLabel() {"
- errorLine2=" ~~~~~~~~~~~~">
- <location
- file="src/main/java/androidx/core/app/RemoteInput.java"
- line="122"
- column="12"/>
- </issue>
-
- <issue
- id="UnknownNullness"
- message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://android.github.io/kotlin-guides/interop.html#nullability-annotations"
- errorLine1=" public CharSequence[] getChoices() {"
- errorLine2=" ~~~~~~~~~~~~~~">
- <location
- file="src/main/java/androidx/core/app/RemoteInput.java"
- line="129"
- column="12"/>
- </issue>
-
- <issue
- id="UnknownNullness"
- message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://android.github.io/kotlin-guides/interop.html#nullability-annotations"
- errorLine1=" public Set<String> getAllowedDataTypes() {"
- errorLine2=" ~~~~~~~~~~~">
- <location
- file="src/main/java/androidx/core/app/RemoteInput.java"
- line="133"
- column="12"/>
- </issue>
-
- <issue
- id="UnknownNullness"
- message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://android.github.io/kotlin-guides/interop.html#nullability-annotations"
- errorLine1=" public Bundle getExtras() {"
- errorLine2=" ~~~~~~">
- <location
- file="src/main/java/androidx/core/app/RemoteInput.java"
- line="170"
- column="12"/>
- </issue>
-
- <issue
- id="UnknownNullness"
- message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://android.github.io/kotlin-guides/interop.html#nullability-annotations"
- errorLine1=" public static Map<String, Uri> getDataResultsFromIntent("
- errorLine2=" ~~~~~~~~~~~~~~~~">
- <location
- file="src/main/java/androidx/core/app/RemoteInput.java"
- line="336"
- column="19"/>
- </issue>
-
- <issue
- id="UnknownNullness"
- message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://android.github.io/kotlin-guides/interop.html#nullability-annotations"
- errorLine1=" Intent intent, String remoteInputResultKey) {"
- errorLine2=" ~~~~~~">
- <location
- file="src/main/java/androidx/core/app/RemoteInput.java"
- line="337"
- column="13"/>
- </issue>
-
- <issue
- id="UnknownNullness"
- message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://android.github.io/kotlin-guides/interop.html#nullability-annotations"
- errorLine1=" Intent intent, String remoteInputResultKey) {"
- errorLine2=" ~~~~~~">
- <location
- file="src/main/java/androidx/core/app/RemoteInput.java"
- line="337"
- column="28"/>
- </issue>
-
- <issue
- id="UnknownNullness"
- message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://android.github.io/kotlin-guides/interop.html#nullability-annotations"
- errorLine1=" public static Bundle getResultsFromIntent(Intent intent) {"
- errorLine2=" ~~~~~~">
- <location
- file="src/main/java/androidx/core/app/RemoteInput.java"
- line="375"
- column="19"/>
- </issue>
-
- <issue
- id="UnknownNullness"
- message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://android.github.io/kotlin-guides/interop.html#nullability-annotations"
- errorLine1=" public static Bundle getResultsFromIntent(Intent intent) {"
- errorLine2=" ~~~~~~">
- <location
- file="src/main/java/androidx/core/app/RemoteInput.java"
- line="375"
- column="47"/>
- </issue>
-
- <issue
- id="UnknownNullness"
- message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://android.github.io/kotlin-guides/interop.html#nullability-annotations"
- errorLine1=" public static void addResultsToIntent(RemoteInput[] remoteInputs, Intent intent,"
- errorLine2=" ~~~~~~~~~~~~~">
- <location
- file="src/main/java/androidx/core/app/RemoteInput.java"
- line="400"
- column="43"/>
- </issue>
-
- <issue
- id="UnknownNullness"
- message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://android.github.io/kotlin-guides/interop.html#nullability-annotations"
- errorLine1=" public static void addResultsToIntent(RemoteInput[] remoteInputs, Intent intent,"
- errorLine2=" ~~~~~~">
- <location
- file="src/main/java/androidx/core/app/RemoteInput.java"
- line="400"
- column="71"/>
- </issue>
-
- <issue
- id="UnknownNullness"
- message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://android.github.io/kotlin-guides/interop.html#nullability-annotations"
- errorLine1=" Bundle results) {"
- errorLine2=" ~~~~~~">
- <location
- file="src/main/java/androidx/core/app/RemoteInput.java"
- line="401"
- column="13"/>
- </issue>
-
- <issue
- id="UnknownNullness"
- message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://android.github.io/kotlin-guides/interop.html#nullability-annotations"
- errorLine1=" public static void addDataResultToIntent(RemoteInput remoteInput, Intent intent,"
- errorLine2=" ~~~~~~~~~~~">
- <location
- file="src/main/java/androidx/core/app/RemoteInput.java"
- line="464"
- column="46"/>
- </issue>
-
- <issue
- id="UnknownNullness"
- message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://android.github.io/kotlin-guides/interop.html#nullability-annotations"
- errorLine1=" public static void addDataResultToIntent(RemoteInput remoteInput, Intent intent,"
- errorLine2=" ~~~~~~">
- <location
- file="src/main/java/androidx/core/app/RemoteInput.java"
- line="464"
- column="71"/>
- </issue>
-
- <issue
- id="UnknownNullness"
- message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://android.github.io/kotlin-guides/interop.html#nullability-annotations"
- errorLine1=" Map<String, Uri> results) {"
- errorLine2=" ~~~~~~~~~~~~~~~~">
- <location
- file="src/main/java/androidx/core/app/RemoteInput.java"
- line="465"
- column="13"/>
- </issue>
-
- <issue
- id="UnknownNullness"
- message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://android.github.io/kotlin-guides/interop.html#nullability-annotations"
errorLine1=" public ResultReceiver(Handler handler) {"
errorLine2=" ~~~~~~~">
<location
@@ -12975,28 +11732,6 @@
<issue
id="UnknownNullness"
message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://android.github.io/kotlin-guides/interop.html#nullability-annotations"
- errorLine1=" public TaskStackBuilder addParentStack(ComponentName sourceActivityName) {"
- errorLine2=" ~~~~~~~~~~~~~~~~">
- <location
- file="src/main/java/androidx/core/app/TaskStackBuilder.java"
- line="200"
- column="12"/>
- </issue>
-
- <issue
- id="UnknownNullness"
- message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://android.github.io/kotlin-guides/interop.html#nullability-annotations"
- errorLine1=" public TaskStackBuilder addParentStack(ComponentName sourceActivityName) {"
- errorLine2=" ~~~~~~~~~~~~~">
- <location
- file="src/main/java/androidx/core/app/TaskStackBuilder.java"
- line="200"
- column="44"/>
- </issue>
-
- <issue
- id="UnknownNullness"
- message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://android.github.io/kotlin-guides/interop.html#nullability-annotations"
errorLine1=" boolean isRtl(char[] array, int start, int count);"
errorLine2=" ~~~~~~">
<location
diff --git a/core/core/src/androidTest/java/androidx/core/database/sqlite/SQLiteCursorCompatTest.java b/core/core/src/androidTest/java/androidx/core/database/sqlite/SQLiteCursorCompatTest.java
index b06bc3c..1a78245 100644
--- a/core/core/src/androidTest/java/androidx/core/database/sqlite/SQLiteCursorCompatTest.java
+++ b/core/core/src/androidTest/java/androidx/core/database/sqlite/SQLiteCursorCompatTest.java
@@ -18,11 +18,8 @@
import static org.junit.Assert.assertTrue;
-import android.database.Cursor;
import android.database.sqlite.SQLiteCursor;
-import android.database.sqlite.SQLiteCursorDriver;
import android.database.sqlite.SQLiteDatabase;
-import android.database.sqlite.SQLiteQuery;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
@@ -36,18 +33,14 @@
@Test
public void setFillWindowForwardOnly() {
final Boolean[] calledSetter = { false };
- SQLiteDatabase db = SQLiteDatabase.create(new SQLiteDatabase.CursorFactory() {
- @Override
- public Cursor newCursor(SQLiteDatabase db, SQLiteCursorDriver primaryQuery,
- String editTable, SQLiteQuery query) {
- SQLiteCursor cursor = new SQLiteCursor(primaryQuery, editTable, query);
- SQLiteCursorCompat.setFillWindowForwardOnly(cursor, true);
+ SQLiteDatabase db = SQLiteDatabase.create((db1, primaryQuery, editTable, query) -> {
+ SQLiteCursor cursor = new SQLiteCursor(primaryQuery, editTable, query);
+ SQLiteCursorCompat.setFillWindowForwardOnly(cursor, true);
- // no easy way to read whether setter worked, so
- // we just validate it can be called successfully
- calledSetter[0] = true;
- return cursor;
- }
+ // no easy way to read whether setter worked, so
+ // we just validate it can be called successfully
+ calledSetter[0] = true;
+ return cursor;
});
db.execSQL("CREATE TABLE foo (num INTEGER);");
db.query("foo", new String[] {"*"}, null, null, null, null, null);
diff --git a/core/core/src/androidTest/java/androidx/core/graphics/BlendModeColorFilterCompatTest.java b/core/core/src/androidTest/java/androidx/core/graphics/BlendModeColorFilterCompatTest.java
index facc1a3..a1222df 100644
--- a/core/core/src/androidTest/java/androidx/core/graphics/BlendModeColorFilterCompatTest.java
+++ b/core/core/src/androidTest/java/androidx/core/graphics/BlendModeColorFilterCompatTest.java
@@ -21,8 +21,6 @@
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
-import android.graphics.BlendMode;
-import android.graphics.BlendModeColorFilter;
import android.graphics.Color;
import android.graphics.ColorFilter;
import android.graphics.PorterDuff;
@@ -37,22 +35,11 @@
import org.junit.runner.RunWith;
@RunWith(AndroidJUnit4.class)
+@SdkSuppress(maxSdkVersion = 28)
@SmallTest
public class BlendModeColorFilterCompatTest {
@Test
- @SdkSuppress(minSdkVersion = Build.VERSION_CODES.Q)
- public void testNullBlendModeRemovesBlendModeColorFilter() {
- ColorFilter filter = createBlendModeColorFilterCompat(Color.RED, BlendModeCompat.CLEAR);
- assertTrue(filter instanceof BlendModeColorFilter);
-
- BlendModeColorFilter blendModeFilter = (BlendModeColorFilter) filter;
- assertEquals(Color.RED, blendModeFilter.getColor());
- assertEquals(BlendMode.CLEAR, blendModeFilter.getMode());
- }
-
- @Test
- @SdkSuppress(maxSdkVersion = Build.VERSION_CODES.P)
public void testNullBlendModeRemovesPorterDuffColorFilter() {
ColorFilter filter = createBlendModeColorFilterCompat(Color.RED, BlendModeCompat.CLEAR);
assertTrue(filter instanceof PorterDuffColorFilter);
diff --git a/core/core/src/androidTest/java/androidx/core/graphics/BlendModeColorFilterCompatTestApi29.java b/core/core/src/androidTest/java/androidx/core/graphics/BlendModeColorFilterCompatTestApi29.java
new file mode 100644
index 0000000..b0ad3b0
--- /dev/null
+++ b/core/core/src/androidTest/java/androidx/core/graphics/BlendModeColorFilterCompatTestApi29.java
@@ -0,0 +1,50 @@
+/*
+ * 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.core.graphics;
+
+import static androidx.core.graphics.BlendModeColorFilterCompat.createBlendModeColorFilterCompat;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+import android.graphics.BlendMode;
+import android.graphics.BlendModeColorFilter;
+import android.graphics.Color;
+import android.graphics.ColorFilter;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.SdkSuppress;
+import androidx.test.filters.SmallTest;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+@SdkSuppress(minSdkVersion = 29)
+@SmallTest
+public class BlendModeColorFilterCompatTestApi29 {
+
+ @Test
+ public void testNullBlendModeRemovesBlendModeColorFilter() {
+ ColorFilter filter = createBlendModeColorFilterCompat(Color.RED, BlendModeCompat.CLEAR);
+ assertTrue(filter instanceof BlendModeColorFilter);
+
+ BlendModeColorFilter blendModeFilter = (BlendModeColorFilter) filter;
+ assertEquals(Color.RED, blendModeFilter.getColor());
+ assertEquals(BlendMode.CLEAR, blendModeFilter.getMode());
+ }
+}
diff --git a/core/core/src/androidTest/java/androidx/core/graphics/PaintTest.java b/core/core/src/androidTest/java/androidx/core/graphics/PaintTest.java
index f6858e3..acbb9cf 100644
--- a/core/core/src/androidTest/java/androidx/core/graphics/PaintTest.java
+++ b/core/core/src/androidTest/java/androidx/core/graphics/PaintTest.java
@@ -17,17 +17,16 @@
package androidx.core.graphics;
import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
-import android.graphics.BlendMode;
+import android.annotation.SuppressLint;
import android.graphics.Paint;
import android.graphics.PorterDuff;
import android.graphics.PorterDuffXfermode;
import android.graphics.Xfermode;
-import android.os.Build;
-import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SdkSuppress;
@@ -37,45 +36,12 @@
import org.junit.runner.RunWith;
@RunWith(AndroidJUnit4.class)
+@SdkSuppress(maxSdkVersion = 28)
@SmallTest
public class PaintTest {
+ @SuppressLint("NewApi") // due to BlendModeCompat's RequiresApi annotations
@Test
- @SdkSuppress(minSdkVersion = Build.VERSION_CODES.Q)
- public void testBlendModeCompatMatchesPlatform() {
- TestHelper.verifyBlendModeMatchesCompat(BlendModeCompat.CLEAR, BlendMode.CLEAR);
- TestHelper.verifyBlendModeMatchesCompat(BlendModeCompat.SRC, BlendMode.SRC);
- TestHelper.verifyBlendModeMatchesCompat(BlendModeCompat.DST, BlendMode.DST);
- TestHelper.verifyBlendModeMatchesCompat(BlendModeCompat.SRC_OVER, BlendMode.SRC_OVER);
- TestHelper.verifyBlendModeMatchesCompat(BlendModeCompat.DST_OVER, BlendMode.DST_OVER);
- TestHelper.verifyBlendModeMatchesCompat(BlendModeCompat.SRC_IN, BlendMode.SRC_IN);
- TestHelper.verifyBlendModeMatchesCompat(BlendModeCompat.DST_IN, BlendMode.DST_IN);
- TestHelper.verifyBlendModeMatchesCompat(BlendModeCompat.SRC_OUT, BlendMode.SRC_OUT);
- TestHelper.verifyBlendModeMatchesCompat(BlendModeCompat.DST_OUT, BlendMode.DST_OUT);
- TestHelper.verifyBlendModeMatchesCompat(BlendModeCompat.SRC_ATOP, BlendMode.SRC_ATOP);
- TestHelper.verifyBlendModeMatchesCompat(BlendModeCompat.DST_ATOP, BlendMode.DST_ATOP);
- TestHelper.verifyBlendModeMatchesCompat(BlendModeCompat.XOR, BlendMode.XOR);
- TestHelper.verifyBlendModeMatchesCompat(BlendModeCompat.PLUS, BlendMode.PLUS);
- TestHelper.verifyBlendModeMatchesCompat(BlendModeCompat.MODULATE, BlendMode.MODULATE);
- TestHelper.verifyBlendModeMatchesCompat(BlendModeCompat.SCREEN, BlendMode.SCREEN);
- TestHelper.verifyBlendModeMatchesCompat(BlendModeCompat.OVERLAY, BlendMode.OVERLAY);
- TestHelper.verifyBlendModeMatchesCompat(BlendModeCompat.DARKEN, BlendMode.DARKEN);
- TestHelper.verifyBlendModeMatchesCompat(BlendModeCompat.LIGHTEN, BlendMode.LIGHTEN);
- TestHelper.verifyBlendModeMatchesCompat(BlendModeCompat.COLOR_DODGE, BlendMode.COLOR_DODGE);
- TestHelper.verifyBlendModeMatchesCompat(BlendModeCompat.COLOR_BURN, BlendMode.COLOR_BURN);
- TestHelper.verifyBlendModeMatchesCompat(BlendModeCompat.HARD_LIGHT, BlendMode.HARD_LIGHT);
- TestHelper.verifyBlendModeMatchesCompat(BlendModeCompat.SOFT_LIGHT, BlendMode.SOFT_LIGHT);
- TestHelper.verifyBlendModeMatchesCompat(BlendModeCompat.DIFFERENCE, BlendMode.DIFFERENCE);
- TestHelper.verifyBlendModeMatchesCompat(BlendModeCompat.EXCLUSION, BlendMode.EXCLUSION);
- TestHelper.verifyBlendModeMatchesCompat(BlendModeCompat.MULTIPLY, BlendMode.MULTIPLY);
- TestHelper.verifyBlendModeMatchesCompat(BlendModeCompat.HUE, BlendMode.HUE);
- TestHelper.verifyBlendModeMatchesCompat(BlendModeCompat.SATURATION, BlendMode.SATURATION);
- TestHelper.verifyBlendModeMatchesCompat(BlendModeCompat.COLOR, BlendMode.COLOR);
- TestHelper.verifyBlendModeMatchesCompat(BlendModeCompat.LUMINOSITY, BlendMode.LUMINOSITY);
- }
-
- @Test
- @SdkSuppress(maxSdkVersion = Build.VERSION_CODES.P)
public void testBlendModeCompatMatchesPorterDuff() {
verifyPorterDuffMatchesCompat(BlendModeCompat.CLEAR, PorterDuff.Mode.CLEAR);
verifyPorterDuffMatchesCompat(BlendModeCompat.SRC, PorterDuff.Mode.SRC);
@@ -113,19 +79,6 @@
}
@Test
- @SdkSuppress(minSdkVersion = Build.VERSION_CODES.Q)
- public void testNullBlendModeRemovesBlendMode() {
- Paint p = new Paint();
- assertTrue(PaintCompat.setBlendMode(p, BlendModeCompat.CLEAR));
- assertEquals(BlendMode.CLEAR, p.getBlendMode());
-
- assertTrue(PaintCompat.setBlendMode(p, null));
- assertNull(p.getBlendMode());
- }
-
-
- @Test
- @SdkSuppress(maxSdkVersion = Build.VERSION_CODES.P)
public void testNullBlendModeRemovesXfermode() {
Paint p = new Paint();
assertTrue(PaintCompat.setBlendMode(p, BlendModeCompat.CLEAR));
@@ -135,19 +88,6 @@
}
/**
- * Helper test class to hide usages of new APIs and avoid ClassNotFoundExceptions
- * in tests
- */
- private static class TestHelper {
- private static void verifyBlendModeMatchesCompat(@NonNull BlendModeCompat compat,
- @NonNull BlendMode blendMode) {
- Paint p = new Paint();
- PaintCompat.setBlendMode(p, compat);
- assertEquals(blendMode, p.getBlendMode());
- }
- }
-
- /**
* Helper method to verify that the provided {@link BlendModeCompat} instance
* matches the given {@link PorterDuff.Mode} which may be null if there is no
* equivalent PorterDuff.Mode for the BlendMode
@@ -159,7 +99,7 @@
if (compat != null && mode == null) {
// If there is not a compatible PorterDuff mode for this BlendMode, configuration
// of the blend mode should return false
- assertTrue(!result);
+ assertFalse(result);
} else if (compat != null) {
// .. otherwise if there is a corresponding PorterDuff mode with the given BlendMode
// then the assignment should complete successfully
diff --git a/core/core/src/androidTest/java/androidx/core/graphics/PaintTestApi29.java b/core/core/src/androidTest/java/androidx/core/graphics/PaintTestApi29.java
new file mode 100644
index 0000000..df91c96
--- /dev/null
+++ b/core/core/src/androidTest/java/androidx/core/graphics/PaintTestApi29.java
@@ -0,0 +1,94 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.core.graphics;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+
+import android.graphics.BlendMode;
+import android.graphics.Paint;
+
+import androidx.annotation.NonNull;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.SdkSuppress;
+import androidx.test.filters.SmallTest;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+@SdkSuppress(minSdkVersion = 29)
+@SmallTest
+public class PaintTestApi29 {
+
+ @Test
+ public void testBlendModeCompatMatchesPlatform() {
+ TestHelper.verifyBlendModeMatchesCompat(BlendModeCompat.CLEAR, BlendMode.CLEAR);
+ TestHelper.verifyBlendModeMatchesCompat(BlendModeCompat.SRC, BlendMode.SRC);
+ TestHelper.verifyBlendModeMatchesCompat(BlendModeCompat.DST, BlendMode.DST);
+ TestHelper.verifyBlendModeMatchesCompat(BlendModeCompat.SRC_OVER, BlendMode.SRC_OVER);
+ TestHelper.verifyBlendModeMatchesCompat(BlendModeCompat.DST_OVER, BlendMode.DST_OVER);
+ TestHelper.verifyBlendModeMatchesCompat(BlendModeCompat.SRC_IN, BlendMode.SRC_IN);
+ TestHelper.verifyBlendModeMatchesCompat(BlendModeCompat.DST_IN, BlendMode.DST_IN);
+ TestHelper.verifyBlendModeMatchesCompat(BlendModeCompat.SRC_OUT, BlendMode.SRC_OUT);
+ TestHelper.verifyBlendModeMatchesCompat(BlendModeCompat.DST_OUT, BlendMode.DST_OUT);
+ TestHelper.verifyBlendModeMatchesCompat(BlendModeCompat.SRC_ATOP, BlendMode.SRC_ATOP);
+ TestHelper.verifyBlendModeMatchesCompat(BlendModeCompat.DST_ATOP, BlendMode.DST_ATOP);
+ TestHelper.verifyBlendModeMatchesCompat(BlendModeCompat.XOR, BlendMode.XOR);
+ TestHelper.verifyBlendModeMatchesCompat(BlendModeCompat.PLUS, BlendMode.PLUS);
+ TestHelper.verifyBlendModeMatchesCompat(BlendModeCompat.MODULATE, BlendMode.MODULATE);
+ TestHelper.verifyBlendModeMatchesCompat(BlendModeCompat.SCREEN, BlendMode.SCREEN);
+ TestHelper.verifyBlendModeMatchesCompat(BlendModeCompat.OVERLAY, BlendMode.OVERLAY);
+ TestHelper.verifyBlendModeMatchesCompat(BlendModeCompat.DARKEN, BlendMode.DARKEN);
+ TestHelper.verifyBlendModeMatchesCompat(BlendModeCompat.LIGHTEN, BlendMode.LIGHTEN);
+ TestHelper.verifyBlendModeMatchesCompat(BlendModeCompat.COLOR_DODGE, BlendMode.COLOR_DODGE);
+ TestHelper.verifyBlendModeMatchesCompat(BlendModeCompat.COLOR_BURN, BlendMode.COLOR_BURN);
+ TestHelper.verifyBlendModeMatchesCompat(BlendModeCompat.HARD_LIGHT, BlendMode.HARD_LIGHT);
+ TestHelper.verifyBlendModeMatchesCompat(BlendModeCompat.SOFT_LIGHT, BlendMode.SOFT_LIGHT);
+ TestHelper.verifyBlendModeMatchesCompat(BlendModeCompat.DIFFERENCE, BlendMode.DIFFERENCE);
+ TestHelper.verifyBlendModeMatchesCompat(BlendModeCompat.EXCLUSION, BlendMode.EXCLUSION);
+ TestHelper.verifyBlendModeMatchesCompat(BlendModeCompat.MULTIPLY, BlendMode.MULTIPLY);
+ TestHelper.verifyBlendModeMatchesCompat(BlendModeCompat.HUE, BlendMode.HUE);
+ TestHelper.verifyBlendModeMatchesCompat(BlendModeCompat.SATURATION, BlendMode.SATURATION);
+ TestHelper.verifyBlendModeMatchesCompat(BlendModeCompat.COLOR, BlendMode.COLOR);
+ TestHelper.verifyBlendModeMatchesCompat(BlendModeCompat.LUMINOSITY, BlendMode.LUMINOSITY);
+ }
+
+ @Test
+ public void testNullBlendModeRemovesBlendMode() {
+ Paint p = new Paint();
+ assertTrue(PaintCompat.setBlendMode(p, BlendModeCompat.CLEAR));
+ assertEquals(BlendMode.CLEAR, p.getBlendMode());
+
+ assertTrue(PaintCompat.setBlendMode(p, null));
+ assertNull(p.getBlendMode());
+ }
+
+ /**
+ * Helper test class to hide usages of new APIs and avoid ClassNotFoundExceptions
+ * in tests
+ */
+ private static class TestHelper {
+ private static void verifyBlendModeMatchesCompat(@NonNull BlendModeCompat compat,
+ @NonNull BlendMode blendMode) {
+ Paint p = new Paint();
+ PaintCompat.setBlendMode(p, compat);
+ assertEquals(blendMode, p.getBlendMode());
+ }
+ }
+}
diff --git a/core/core/src/androidTest/java/androidx/core/view/WindowInsetsAnimationCompatActivityTest.kt b/core/core/src/androidTest/java/androidx/core/view/WindowInsetsAnimationCompatActivityTest.kt
index 88dec5b..04000e8 100644
--- a/core/core/src/androidTest/java/androidx/core/view/WindowInsetsAnimationCompatActivityTest.kt
+++ b/core/core/src/androidTest/java/androidx/core/view/WindowInsetsAnimationCompatActivityTest.kt
@@ -31,6 +31,7 @@
import androidx.test.espresso.Espresso.onView
import androidx.test.espresso.action.ViewActions
import androidx.test.espresso.matcher.ViewMatchers.withId
+import androidx.test.filters.FlakyTest
import androidx.test.filters.LargeTest
import androidx.test.filters.SdkSuppress
import androidx.testutils.withActivity
@@ -404,6 +405,50 @@
}
@Test
+ public fun check_view_on_apply_called() {
+ val container = scenario.withActivity { findViewById(R.id.container) }
+ val onApplyLatch = CountDownLatch(1)
+ val customView = object : View(scenario.withActivity { this }) {
+ override fun onApplyWindowInsets(insets: WindowInsets): WindowInsets {
+ onApplyLatch.countDown()
+ return insets
+ }
+ }
+ scenario.onActivity { (container as ViewGroup).addView(customView) }
+ val stopCallback = createCallback()
+ ViewCompat.setWindowInsetsAnimationCallback(customView, stopCallback)
+ triggerInsetAnimation(container)
+ assertTrue(
+ "The View.onApplyWindowInsets has not been called",
+ onApplyLatch.await(2, TimeUnit.SECONDS)
+ )
+ }
+
+ private fun createCallback(
+ dispatchMode: Int = DISPATCH_MODE_CONTINUE_ON_SUBTREE,
+ onPrepare: ((WindowInsetsAnimationCompat) -> Unit)? = null,
+ onEnd: ((WindowInsetsAnimationCompat) -> Unit)? = null
+ ): WindowInsetsAnimationCompat.Callback {
+ return object :
+ WindowInsetsAnimationCompat.Callback(dispatchMode) {
+
+ override fun onPrepare(animation: WindowInsetsAnimationCompat) {
+ onPrepare?.invoke(animation)
+ }
+
+ override fun onProgress(
+ insets: WindowInsetsCompat,
+ runningAnimations: MutableList<WindowInsetsAnimationCompat>
+ ): WindowInsetsCompat = insets
+
+ override fun onEnd(animation: WindowInsetsAnimationCompat) {
+ onEnd?.invoke(animation)
+ }
+ }
+ }
+
+ @Test
+ @FlakyTest(bugId = 196917541)
public fun child_callback_not_called_when_dispatch_stop() {
assumeNotCuttlefish()
@@ -473,49 +518,6 @@
)
}
- @Test
- public fun check_view_on_apply_called() {
- val container = scenario.withActivity { findViewById(R.id.container) }
- val onApplyLatch = CountDownLatch(1)
- val customView = object : View(scenario.withActivity { this }) {
- override fun onApplyWindowInsets(insets: WindowInsets): WindowInsets {
- onApplyLatch.countDown()
- return insets
- }
- }
- scenario.onActivity { (container as ViewGroup).addView(customView) }
- val stopCallback = createCallback()
- ViewCompat.setWindowInsetsAnimationCallback(customView, stopCallback)
- triggerInsetAnimation(container)
- assertTrue(
- "The View.onApplyWindowInsets has not been called",
- onApplyLatch.await(2, TimeUnit.SECONDS)
- )
- }
-
- private fun createCallback(
- dispatchMode: Int = DISPATCH_MODE_CONTINUE_ON_SUBTREE,
- onPrepare: ((WindowInsetsAnimationCompat) -> Unit)? = null,
- onEnd: ((WindowInsetsAnimationCompat) -> Unit)? = null
- ): WindowInsetsAnimationCompat.Callback {
- return object :
- WindowInsetsAnimationCompat.Callback(dispatchMode) {
-
- override fun onPrepare(animation: WindowInsetsAnimationCompat) {
- onPrepare?.invoke(animation)
- }
-
- override fun onProgress(
- insets: WindowInsetsCompat,
- runningAnimations: MutableList<WindowInsetsAnimationCompat>
- ): WindowInsetsCompat = insets
-
- override fun onEnd(animation: WindowInsetsAnimationCompat) {
- onEnd?.invoke(animation)
- }
- }
- }
-
private fun assumeNotCuttlefish() {
// TODO: remove this if b/159103848 is resolved
Assume.assumeFalse(
diff --git a/core/core/src/main/java/androidx/core/app/Person.java b/core/core/src/main/java/androidx/core/app/Person.java
index cb95bb7..32bfb95 100644
--- a/core/core/src/main/java/androidx/core/app/Person.java
+++ b/core/core/src/main/java/androidx/core/app/Person.java
@@ -21,6 +21,7 @@
import android.os.Bundle;
import android.os.PersistableBundle;
+import androidx.annotation.DoNotInline;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.RequiresApi;
@@ -67,13 +68,7 @@
@NonNull
@RequiresApi(22)
public static Person fromPersistableBundle(@NonNull PersistableBundle bundle) {
- return new Builder()
- .setName(bundle.getString(NAME_KEY))
- .setUri(bundle.getString(URI_KEY))
- .setKey(bundle.getString(KEY_KEY))
- .setBot(bundle.getBoolean(IS_BOT_KEY))
- .setImportant(bundle.getBoolean(IS_IMPORTANT_KEY))
- .build();
+ return Api22Impl.fromPersistableBundle(bundle);
}
/**
@@ -85,17 +80,7 @@
@RequiresApi(28)
@NonNull
public static Person fromAndroidPerson(@NonNull android.app.Person person) {
- return new Builder()
- .setName(person.getName())
- .setIcon(
- (person.getIcon() != null)
- ? IconCompat.createFromIcon(person.getIcon())
- : null)
- .setUri(person.getUri())
- .setKey(person.getKey())
- .setBot(person.isBot())
- .setImportant(person.isImportant())
- .build();
+ return Api28Impl.fromAndroidPerson(person);
}
@SuppressWarnings("WeakerAccess") /* synthetic access */
@@ -148,13 +133,7 @@
@NonNull
@RequiresApi(22)
public PersistableBundle toPersistableBundle() {
- PersistableBundle result = new PersistableBundle();
- result.putString(NAME_KEY, mName != null ? mName.toString() : null);
- result.putString(URI_KEY, mUri);
- result.putString(KEY_KEY, mKey);
- result.putBoolean(IS_BOT_KEY, mIsBot);
- result.putBoolean(IS_IMPORTANT_KEY, mIsImportant);
- return result;
+ return Api22Impl.toPersistableBundle(this);
}
/** Creates and returns a new {@link Builder} initialized with this Person's data. */
@@ -172,14 +151,7 @@
@NonNull
@RequiresApi(28)
public android.app.Person toAndroidPerson() {
- return new android.app.Person.Builder()
- .setName(getName())
- .setIcon((getIcon() != null) ? getIcon().toIcon() : null)
- .setUri(getUri())
- .setKey(getKey())
- .setBot(isBot())
- .setImportant(isImportant())
- .build();
+ return Api28Impl.toAndroidPerson(this);
}
/**
@@ -356,4 +328,68 @@
return new Person(this);
}
}
+
+ @RequiresApi(22)
+ static class Api22Impl {
+ private Api22Impl() {
+ // This class is not instantiable.
+ }
+
+ @DoNotInline
+ static Person fromPersistableBundle(PersistableBundle bundle) {
+ return new Builder()
+ .setName(bundle.getString(NAME_KEY))
+ .setUri(bundle.getString(URI_KEY))
+ .setKey(bundle.getString(KEY_KEY))
+ .setBot(bundle.getBoolean(IS_BOT_KEY))
+ .setImportant(bundle.getBoolean(IS_IMPORTANT_KEY))
+ .build();
+ }
+
+ @DoNotInline
+ static PersistableBundle toPersistableBundle(Person person) {
+ PersistableBundle result = new PersistableBundle();
+ result.putString(NAME_KEY, person.mName != null ? person.mName.toString() : null);
+ result.putString(URI_KEY, person.mUri);
+ result.putString(KEY_KEY, person.mKey);
+ result.putBoolean(IS_BOT_KEY, person.mIsBot);
+ result.putBoolean(IS_IMPORTANT_KEY, person.mIsImportant);
+ return result;
+ }
+ }
+
+ @RequiresApi(28)
+ static class Api28Impl {
+ private Api28Impl() {
+ // This class is not instantiable.
+ }
+
+ @DoNotInline
+ static Person fromAndroidPerson(android.app.Person person) {
+ return new Builder()
+ .setName(person.getName())
+ .setIcon(
+ (person.getIcon() != null)
+ ? IconCompat.createFromIcon(person.getIcon())
+ : null)
+ .setUri(person.getUri())
+ .setKey(person.getKey())
+ .setBot(person.isBot())
+ .setImportant(person.isImportant())
+ .build();
+ }
+
+ @SuppressWarnings("deprecation")
+ @DoNotInline
+ static android.app.Person toAndroidPerson(Person person) {
+ return new android.app.Person.Builder()
+ .setName(person.getName())
+ .setIcon((person.getIcon() != null) ? person.getIcon().toIcon() : null)
+ .setUri(person.getUri())
+ .setKey(person.getKey())
+ .setBot(person.isBot())
+ .setImportant(person.isImportant())
+ .build();
+ }
+ }
}
diff --git a/core/core/src/main/java/androidx/core/app/RemoteActionCompat.java b/core/core/src/main/java/androidx/core/app/RemoteActionCompat.java
index 8363a1a..9023394 100644
--- a/core/core/src/main/java/androidx/core/app/RemoteActionCompat.java
+++ b/core/core/src/main/java/androidx/core/app/RemoteActionCompat.java
@@ -18,10 +18,13 @@
import static androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP;
+import android.annotation.SuppressLint;
import android.app.PendingIntent;
import android.app.RemoteAction;
+import android.graphics.drawable.Icon;
import android.os.Build;
+import androidx.annotation.DoNotInline;
import androidx.annotation.NonNull;
import androidx.annotation.RequiresApi;
import androidx.annotation.RestrictTo;
@@ -42,24 +45,32 @@
/**
* @hide
*/
+ @SuppressWarnings("NotNullFieldNotInitialized") // VersionedParceleble inits this field.
+ @NonNull
@RestrictTo(LIBRARY_GROUP)
@ParcelField(1)
public IconCompat mIcon;
/**
* @hide
*/
+ @SuppressWarnings("NotNullFieldNotInitialized") // VersionedParceleble inits this field.
+ @NonNull
@RestrictTo(LIBRARY_GROUP)
@ParcelField(2)
public CharSequence mTitle;
/**
* @hide
*/
+ @SuppressWarnings("NotNullFieldNotInitialized") // VersionedParceleble inits this field.
+ @NonNull
@RestrictTo(LIBRARY_GROUP)
@ParcelField(3)
public CharSequence mContentDescription;
/**
* @hide
*/
+ @SuppressWarnings("NotNullFieldNotInitialized") // VersionedParceleble inits this field.
+ @NonNull
@RestrictTo(LIBRARY_GROUP)
@ParcelField(4)
public PendingIntent mActionIntent;
@@ -113,12 +124,14 @@
@NonNull
public static RemoteActionCompat createFromRemoteAction(@NonNull RemoteAction remoteAction) {
Preconditions.checkNotNull(remoteAction);
- RemoteActionCompat action = new RemoteActionCompat(
- IconCompat.createFromIcon(remoteAction.getIcon()), remoteAction.getTitle(),
- remoteAction.getContentDescription(), remoteAction.getActionIntent());
- action.setEnabled(remoteAction.isEnabled());
+ RemoteActionCompat action = new RemoteActionCompat(IconCompat.createFromIcon(
+ Api26Impl.getIcon(remoteAction)),
+ Api26Impl.getTitle(remoteAction),
+ Api26Impl.getContentDescription(remoteAction),
+ Api26Impl.getActionIntent(remoteAction));
+ action.setEnabled(Api26Impl.isEnabled(remoteAction));
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
- action.setShouldShowIcon(remoteAction.shouldShowIcon());
+ action.setShouldShowIcon(Api28Impl.shouldShowIcon(remoteAction));
}
return action;
}
@@ -147,6 +160,7 @@
/**
* Return whether the icon should be shown.
*/
+ @SuppressLint("KotlinPropertyAccess")
public boolean shouldShowIcon() {
return mShouldShowIcon;
}
@@ -184,15 +198,76 @@
*
* @return {@link RemoteAction} object
*/
+ @SuppressWarnings("deprecation")
@RequiresApi(26)
@NonNull
public RemoteAction toRemoteAction() {
- RemoteAction action = new RemoteAction(mIcon.toIcon(), mTitle, mContentDescription,
- mActionIntent);
- action.setEnabled(isEnabled());
+ RemoteAction action = Api26Impl.createRemoteAction(mIcon.toIcon(), mTitle,
+ mContentDescription, mActionIntent);
+ Api26Impl.setEnabled(action, isEnabled());
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
- action.setShouldShowIcon(shouldShowIcon());
+ Api28Impl.setShouldShowIcon(action, shouldShowIcon());
}
return action;
}
+
+ @RequiresApi(28)
+ static class Api28Impl {
+ private Api28Impl() {
+ // This class is not instantiable.
+ }
+
+ @DoNotInline
+ static boolean shouldShowIcon(RemoteAction remoteAction) {
+ return remoteAction.shouldShowIcon();
+ }
+
+ @DoNotInline
+ static void setShouldShowIcon(RemoteAction remoteAction, boolean shouldShowIcon) {
+ remoteAction.setShouldShowIcon(shouldShowIcon);
+ }
+ }
+
+ @RequiresApi(26)
+ static class Api26Impl {
+ private Api26Impl() {
+ // This class is not instantiable.
+ }
+
+ @DoNotInline
+ static CharSequence getContentDescription(RemoteAction remoteAction) {
+ return remoteAction.getContentDescription();
+ }
+
+ @DoNotInline
+ static PendingIntent getActionIntent(RemoteAction remoteAction) {
+ return remoteAction.getActionIntent();
+ }
+
+ @DoNotInline
+ static CharSequence getTitle(RemoteAction remoteAction) {
+ return remoteAction.getTitle();
+ }
+
+ @DoNotInline
+ static Icon getIcon(RemoteAction remoteAction) {
+ return remoteAction.getIcon();
+ }
+
+ @DoNotInline
+ static boolean isEnabled(RemoteAction remoteAction) {
+ return remoteAction.isEnabled();
+ }
+
+ @DoNotInline
+ static RemoteAction createRemoteAction(Icon icon, CharSequence title,
+ CharSequence contentDescription, PendingIntent intent) {
+ return new RemoteAction(icon, title, contentDescription, intent);
+ }
+
+ @DoNotInline
+ static void setEnabled(RemoteAction remoteAction, boolean enabled) {
+ remoteAction.setEnabled(enabled);
+ }
+ }
}
diff --git a/core/core/src/main/java/androidx/core/app/RemoteInput.java b/core/core/src/main/java/androidx/core/app/RemoteInput.java
index ecbe7de..924e084 100644
--- a/core/core/src/main/java/androidx/core/app/RemoteInput.java
+++ b/core/core/src/main/java/androidx/core/app/RemoteInput.java
@@ -23,6 +23,7 @@
import android.os.Build;
import android.os.Bundle;
+import androidx.annotation.DoNotInline;
import androidx.annotation.IntDef;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
@@ -40,7 +41,6 @@
* Helper for using the {@link android.app.RemoteInput}.
*/
public final class RemoteInput {
- private static final String TAG = "RemoteInput";
/** Label used to denote the clip data type used for remote input transport */
public static final String RESULTS_CLIP_LABEL = "android.remoteinput.results";
@@ -112,6 +112,7 @@
* Get the key that the result of this input will be set in from the Bundle returned by
* {@link #getResultsFromIntent} when the {@link android.app.PendingIntent} is sent.
*/
+ @NonNull
public String getResultKey() {
return mResultKey;
}
@@ -119,6 +120,7 @@
/**
* Get the label to display to users when collecting this input.
*/
+ @Nullable
public CharSequence getLabel() {
return mLabel;
}
@@ -126,10 +128,14 @@
/**
* Get possible input choices. This can be {@code null} if there are no choices to present.
*/
+ @SuppressWarnings("NullableCollection") // Look, it's not the best API.
+ @Nullable
public CharSequence[] getChoices() {
return mChoices;
}
+ @SuppressWarnings("NullableCollection") // That's just how it was defined.
+ @Nullable
public Set<String> getAllowedDataTypes() {
return mAllowedDataTypes;
}
@@ -167,6 +173,7 @@
/**
* Get additional metadata carried around with this remote input.
*/
+ @NonNull
public Bundle getExtras() {
return mExtras;
}
@@ -333,10 +340,12 @@
* which also had one or more remote input requested.
* @param remoteInputResultKey The result key for the RemoteInput you want results for.
*/
+ @SuppressWarnings("NullableCollection") // This is what the platform API does.
+ @Nullable
public static Map<String, Uri> getDataResultsFromIntent(
- Intent intent, String remoteInputResultKey) {
+ @NonNull Intent intent, @NonNull String remoteInputResultKey) {
if (Build.VERSION.SDK_INT >= 26) {
- return android.app.RemoteInput.getDataResultsFromIntent(intent, remoteInputResultKey);
+ return Api26Impl.getDataResultsFromIntent(intent, remoteInputResultKey);
} else if (Build.VERSION.SDK_INT >= 16) {
Intent clipDataIntent = getClipDataIntentFromIntent(intent);
if (clipDataIntent == null) {
@@ -372,9 +381,11 @@
* @param intent The intent object that fired in response to an action or content intent
* which also had one or more remote input requested.
*/
- public static Bundle getResultsFromIntent(Intent intent) {
+ @SuppressWarnings("NullableCollection") // This is on purpose.
+ @Nullable
+ public static Bundle getResultsFromIntent(@NonNull Intent intent) {
if (Build.VERSION.SDK_INT >= 20) {
- return android.app.RemoteInput.getResultsFromIntent(intent);
+ return Api20Impl.getResultsFromIntent(intent);
} else if (Build.VERSION.SDK_INT >= 16) {
Intent clipDataIntent = getClipDataIntentFromIntent(intent);
if (clipDataIntent == null) {
@@ -397,16 +408,16 @@
* be populated with keys matching the result keys specified in
* {@code remoteInputs} with values being the result per key.
*/
- public static void addResultsToIntent(RemoteInput[] remoteInputs, Intent intent,
- Bundle results) {
+ public static void addResultsToIntent(@NonNull RemoteInput[] remoteInputs,
+ @NonNull Intent intent, @NonNull Bundle results) {
if (Build.VERSION.SDK_INT >= 26) {
- android.app.RemoteInput.addResultsToIntent(fromCompat(remoteInputs), intent, results);
+ Api20Impl.addResultsToIntent(fromCompat(remoteInputs), intent, results);
} else if (Build.VERSION.SDK_INT >= 20) {
// Implementations of RemoteInput#addResultsToIntent prior to SDK 26 don't actually add
// results, they wipe out old results and insert the new one. Work around that by
// preserving old results.
Bundle existingTextResults =
- androidx.core.app.RemoteInput.getResultsFromIntent(intent);
+ RemoteInput.getResultsFromIntent(intent);
// We also need to preserve the results source, as it is also cleared.
int resultsSource = getResultsSource(intent);
@@ -419,12 +430,11 @@
for (RemoteInput input : remoteInputs) {
// Data results are also wiped out. So grab them and add them back in.
Map<String, Uri> existingDataResults =
- androidx.core.app.RemoteInput.getDataResultsFromIntent(
+ RemoteInput.getDataResultsFromIntent(
intent, input.getResultKey());
RemoteInput[] arr = new RemoteInput[1];
arr[0] = input;
- android.app.RemoteInput.addResultsToIntent(
- fromCompat(arr), intent, existingTextResults);
+ Api20Impl.addResultsToIntent(fromCompat(arr), intent, existingTextResults);
if (existingDataResults != null) {
RemoteInput.addDataResultToIntent(input, intent, existingDataResults);
}
@@ -449,7 +459,8 @@
}
}
clipDataIntent.putExtra(RemoteInput.EXTRA_RESULTS_DATA, resultsBundle);
- intent.setClipData(ClipData.newIntent(RemoteInput.RESULTS_CLIP_LABEL, clipDataIntent));
+ Api16Impl.setClipData(intent,
+ ClipData.newIntent(RemoteInput.RESULTS_CLIP_LABEL, clipDataIntent));
}
}
@@ -457,14 +468,14 @@
* Same as {@link #addResultsToIntent} but for setting data results.
* @param remoteInput The remote input for which results are being provided
* @param intent The intent to add remote input results to. The
- * {@link android.content.ClipData} field of the intent will be
+ * {@link ClipData} field of the intent will be
* modified to contain the results.
* @param results A map of mime type to the Uri result for that mime type.
*/
- public static void addDataResultToIntent(RemoteInput remoteInput, Intent intent,
- Map<String, Uri> results) {
+ public static void addDataResultToIntent(@NonNull RemoteInput remoteInput,
+ @NonNull Intent intent, @NonNull Map<String, Uri> results) {
if (Build.VERSION.SDK_INT >= 26) {
- android.app.RemoteInput.addDataResultToIntent(fromCompat(remoteInput), intent, results);
+ Api26Impl.addDataResultToIntent(remoteInput, intent, results);
} else if (Build.VERSION.SDK_INT >= 16) {
Intent clipDataIntent = getClipDataIntentFromIntent(intent);
if (clipDataIntent == null) {
@@ -484,7 +495,8 @@
resultsBundle.putString(remoteInput.getResultKey(), uri.toString());
clipDataIntent.putExtra(getExtraResultsKeyForData(mimeType), resultsBundle);
}
- intent.setClipData(ClipData.newIntent(RemoteInput.RESULTS_CLIP_LABEL, clipDataIntent));
+ Api16Impl.setClipData(intent,
+ ClipData.newIntent(RemoteInput.RESULTS_CLIP_LABEL, clipDataIntent));
}
}
@@ -503,14 +515,14 @@
*/
public static void setResultsSource(@NonNull Intent intent, @Source int source) {
if (Build.VERSION.SDK_INT >= 28) {
- android.app.RemoteInput.setResultsSource(intent, source);
+ Api28Impl.setResultsSource(intent, source);
} else if (Build.VERSION.SDK_INT >= 16) {
Intent clipDataIntent = getClipDataIntentFromIntent(intent);
if (clipDataIntent == null) {
clipDataIntent = new Intent(); // First time we've added a result.
}
clipDataIntent.putExtra(EXTRA_RESULTS_SOURCE, source);
- intent.setClipData(ClipData.newIntent(RESULTS_CLIP_LABEL, clipDataIntent));
+ Api16Impl.setClipData(intent, ClipData.newIntent(RESULTS_CLIP_LABEL, clipDataIntent));
}
}
@@ -528,7 +540,7 @@
@Source
public static int getResultsSource(@NonNull Intent intent) {
if (Build.VERSION.SDK_INT >= 28) {
- return android.app.RemoteInput.getResultsSource(intent);
+ return Api28Impl.getResultsSource(intent);
} else if (Build.VERSION.SDK_INT >= 16) {
Intent clipDataIntent = getClipDataIntentFromIntent(intent);
if (clipDataIntent == null) {
@@ -558,51 +570,17 @@
@RequiresApi(20)
static android.app.RemoteInput fromCompat(RemoteInput src) {
- android.app.RemoteInput.Builder builder =
- new android.app.RemoteInput.Builder(src.getResultKey())
- .setLabel(src.getLabel())
- .setChoices(src.getChoices())
- .setAllowFreeFormInput(src.getAllowFreeFormInput())
- .addExtras(src.getExtras());
- if (Build.VERSION.SDK_INT >= 26) {
- Set<String> allowedDataTypes = src.getAllowedDataTypes();
- if (allowedDataTypes != null) {
- for (String allowedDataType : allowedDataTypes) {
- builder.setAllowDataType(allowedDataType, true);
- }
- }
- }
- if (Build.VERSION.SDK_INT >= 29) {
- builder.setEditChoicesBeforeSending(src.getEditChoicesBeforeSending());
- }
- return builder.build();
+ return Api20Impl.fromCompat(src);
}
@RequiresApi(20)
static RemoteInput fromPlatform(android.app.RemoteInput src) {
- RemoteInput.Builder builder =
- new RemoteInput.Builder(src.getResultKey())
- .setLabel(src.getLabel())
- .setChoices(src.getChoices())
- .setAllowFreeFormInput(src.getAllowFreeFormInput())
- .addExtras(src.getExtras());
- if (Build.VERSION.SDK_INT >= 26) {
- Set<String> allowedDataTypes = src.getAllowedDataTypes();
- if (allowedDataTypes != null) {
- for (String allowedDataType : allowedDataTypes) {
- builder.setAllowDataType(allowedDataType, true);
- }
- }
- }
- if (Build.VERSION.SDK_INT >= 29) {
- builder.setEditChoicesBeforeSending(src.getEditChoicesBeforeSending());
- }
- return builder.build();
+ return Api20Impl.fromPlatform(src);
}
@RequiresApi(16)
private static Intent getClipDataIntentFromIntent(Intent intent) {
- ClipData clipData = intent.getClipData();
+ ClipData clipData = Api16Impl.getClipData(intent);
if (clipData == null) {
return null;
}
@@ -615,4 +593,147 @@
}
return clipData.getItemAt(0).getIntent();
}
+
+ @RequiresApi(26)
+ static class Api26Impl {
+ private Api26Impl() {
+ // This class is not instantiable.
+ }
+
+ @DoNotInline
+ static Map<String, Uri> getDataResultsFromIntent(Intent intent,
+ String remoteInputResultKey) {
+ return android.app.RemoteInput.getDataResultsFromIntent(intent, remoteInputResultKey);
+ }
+
+ @DoNotInline
+ static Set<String> getAllowedDataTypes(Object remoteInput) {
+ return ((android.app.RemoteInput) remoteInput).getAllowedDataTypes();
+ }
+
+ @DoNotInline
+ static void addDataResultToIntent(RemoteInput remoteInput, Intent intent,
+ Map<String, Uri> results) {
+ android.app.RemoteInput.addDataResultToIntent(fromCompat(remoteInput), intent, results);
+ }
+
+ @DoNotInline
+ static android.app.RemoteInput.Builder setAllowDataType(
+ android.app.RemoteInput.Builder builder, String mimeType, boolean doAllow) {
+ return builder.setAllowDataType(mimeType, doAllow);
+ }
+ }
+
+ @RequiresApi(20)
+ static class Api20Impl {
+ private Api20Impl() {
+ // This class is not instantiable.
+ }
+
+ @DoNotInline
+ static Bundle getResultsFromIntent(Intent intent) {
+ return android.app.RemoteInput.getResultsFromIntent(intent);
+ }
+
+ @DoNotInline
+ static void addResultsToIntent(Object remoteInputs, Intent intent, Bundle results) {
+ android.app.RemoteInput.addResultsToIntent((android.app.RemoteInput[]) remoteInputs,
+ intent, results);
+ }
+
+ static RemoteInput fromPlatform(Object srcObj) {
+ android.app.RemoteInput src = (android.app.RemoteInput) srcObj;
+ Builder builder =
+ new Builder(src.getResultKey())
+ .setLabel(src.getLabel())
+ .setChoices(src.getChoices())
+ .setAllowFreeFormInput(src.getAllowFreeFormInput())
+ .addExtras(src.getExtras());
+ if (Build.VERSION.SDK_INT >= 26) {
+ Set<String> allowedDataTypes = Api26Impl.getAllowedDataTypes(src);
+ if (allowedDataTypes != null) {
+ for (String allowedDataType : allowedDataTypes) {
+ builder.setAllowDataType(allowedDataType, true);
+ }
+ }
+ }
+ if (Build.VERSION.SDK_INT >= 29) {
+ builder.setEditChoicesBeforeSending(Api29Impl.getEditChoicesBeforeSending(src));
+ }
+ return builder.build();
+ }
+
+ public static android.app.RemoteInput fromCompat(RemoteInput src) {
+ android.app.RemoteInput.Builder builder =
+ new android.app.RemoteInput.Builder(src.getResultKey())
+ .setLabel(src.getLabel())
+ .setChoices(src.getChoices())
+ .setAllowFreeFormInput(src.getAllowFreeFormInput())
+ .addExtras(src.getExtras());
+ if (Build.VERSION.SDK_INT >= 26) {
+ Set<String> allowedDataTypes = src.getAllowedDataTypes();
+ if (allowedDataTypes != null) {
+ for (String allowedDataType : allowedDataTypes) {
+ Api26Impl.setAllowDataType(builder, allowedDataType, true);
+ }
+ }
+ }
+ if (Build.VERSION.SDK_INT >= 29) {
+ Api29Impl.setEditChoicesBeforeSending(builder, src.getEditChoicesBeforeSending());
+ }
+ return builder.build();
+ }
+ }
+
+ @RequiresApi(16)
+ static class Api16Impl {
+ private Api16Impl() {
+ // This class is not instantiable.
+ }
+
+ @DoNotInline
+ static ClipData getClipData(Intent intent) {
+ return intent.getClipData();
+ }
+
+ @DoNotInline
+ static void setClipData(Intent intent, ClipData clip) {
+ intent.setClipData(clip);
+ }
+ }
+
+ @RequiresApi(29)
+ static class Api29Impl {
+ private Api29Impl() {
+ // This class is not instantiable.
+ }
+
+ @DoNotInline
+ static int getEditChoicesBeforeSending(Object remoteInput) {
+ return ((android.app.RemoteInput) remoteInput).getEditChoicesBeforeSending();
+ }
+
+ @DoNotInline
+ static android.app.RemoteInput.Builder setEditChoicesBeforeSending(
+ android.app.RemoteInput.Builder builder, int editChoicesBeforeSending) {
+ return builder.setEditChoicesBeforeSending(editChoicesBeforeSending);
+ }
+ }
+
+ @RequiresApi(28)
+ static class Api28Impl {
+ private Api28Impl() {
+ // This class is not instantiable.
+ }
+
+ @DoNotInline
+ static void setResultsSource(Intent intent, int source) {
+ android.app.RemoteInput.setResultsSource(intent, source);
+ }
+
+ @DoNotInline
+ static int getResultsSource(Intent intent) {
+ return android.app.RemoteInput.getResultsSource(intent);
+ }
+ }
}
diff --git a/core/core/src/main/java/androidx/core/app/ServiceCompat.java b/core/core/src/main/java/androidx/core/app/ServiceCompat.java
index 320ece3..e0ef45d 100644
--- a/core/core/src/main/java/androidx/core/app/ServiceCompat.java
+++ b/core/core/src/main/java/androidx/core/app/ServiceCompat.java
@@ -22,8 +22,10 @@
import android.app.Service;
import android.os.Build;
+import androidx.annotation.DoNotInline;
import androidx.annotation.IntDef;
import androidx.annotation.NonNull;
+import androidx.annotation.RequiresApi;
import androidx.annotation.RestrictTo;
import java.lang.annotation.Retention;
@@ -97,9 +99,21 @@
@SuppressWarnings("deprecation")
public static void stopForeground(@NonNull Service service, @StopForegroundFlags int flags) {
if (Build.VERSION.SDK_INT >= 24) {
- service.stopForeground(flags);
+ Api24Impl.stopForeground(service, flags);
} else {
service.stopForeground((flags & ServiceCompat.STOP_FOREGROUND_REMOVE) != 0);
}
}
+
+ @RequiresApi(24)
+ static class Api24Impl {
+ private Api24Impl() {
+ // This class is not instantiable.
+ }
+
+ @DoNotInline
+ static void stopForeground(Service service, int flags) {
+ service.stopForeground(flags);
+ }
+ }
}
diff --git a/core/core/src/main/java/androidx/core/app/ShareCompat.java b/core/core/src/main/java/androidx/core/app/ShareCompat.java
index 8d6b8ac..c209d4f 100644
--- a/core/core/src/main/java/androidx/core/app/ShareCompat.java
+++ b/core/core/src/main/java/androidx/core/app/ShareCompat.java
@@ -38,6 +38,7 @@
import android.view.MenuItem;
import android.widget.ShareActionProvider;
+import androidx.annotation.DoNotInline;
import androidx.annotation.IdRes;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
@@ -265,6 +266,7 @@
*
* @deprecated Use the system sharesheet. See https://developer.android.com/training/sharing/send
*/
+ @SuppressWarnings("deprecation")
@Deprecated
public static void configureMenuItem(@NonNull Menu menu, @IdRes int menuItemId,
@NonNull IntentBuilder shareIntent) {
@@ -832,7 +834,7 @@
result = Html.toHtml((Spanned) text);
} else if (text != null) {
if (SDK_INT >= 16) {
- result = Html.escapeHtml(text);
+ result = Api16Impl.escapeHtml(text);
} else {
StringBuilder out = new StringBuilder();
withinStyle(out, text, 0, text.length());
@@ -882,7 +884,7 @@
*/
@Nullable
public Uri getStream() {
- return (Uri) mIntent.getParcelableExtra(Intent.EXTRA_STREAM);
+ return mIntent.getParcelableExtra(Intent.EXTRA_STREAM);
}
/**
@@ -1080,9 +1082,11 @@
@RequiresApi(16)
private static class Api16Impl {
- // Prevent instantiation.
- private Api16Impl() {}
+ private Api16Impl() {
+ // This class is not instantiable.
+ }
+ @DoNotInline
static void migrateExtraStreamToClipData(@NonNull Intent intent,
@NonNull ArrayList<Uri> streams) {
CharSequence text = intent.getCharSequenceExtra(Intent.EXTRA_TEXT);
@@ -1101,9 +1105,15 @@
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
}
+ @DoNotInline
static void removeClipData(@NonNull Intent intent) {
intent.setClipData(null);
intent.setFlags(intent.getFlags() & ~Intent.FLAG_GRANT_READ_URI_PERMISSION);
}
+
+ @DoNotInline
+ static String escapeHtml(CharSequence text) {
+ return Html.escapeHtml(text);
+ }
}
}
diff --git a/core/core/src/main/java/androidx/core/app/TaskStackBuilder.java b/core/core/src/main/java/androidx/core/app/TaskStackBuilder.java
index 3a881a6c..db30332 100644
--- a/core/core/src/main/java/androidx/core/app/TaskStackBuilder.java
+++ b/core/core/src/main/java/androidx/core/app/TaskStackBuilder.java
@@ -26,8 +26,10 @@
import android.os.Bundle;
import android.util.Log;
+import androidx.annotation.DoNotInline;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
+import androidx.annotation.RequiresApi;
import androidx.core.content.ContextCompat;
import java.util.ArrayList;
@@ -76,7 +78,7 @@
Intent getSupportParentActivityIntent();
}
- private final ArrayList<Intent> mIntents = new ArrayList<Intent>();
+ private final ArrayList<Intent> mIntents = new ArrayList<>();
private final Context mSourceContext;
private TaskStackBuilder(Context a) {
@@ -197,7 +199,8 @@
* this activity will be added
* @return This TaskStackBuilder for method chaining
*/
- public TaskStackBuilder addParentStack(ComponentName sourceActivityName) {
+ @NonNull
+ public TaskStackBuilder addParentStack(@NonNull ComponentName sourceActivityName) {
final int insertAt = mIntents.size();
try {
Intent parent = NavUtils.getParentActivityIntent(mSourceContext, sourceActivityName);
@@ -250,6 +253,7 @@
/**
* @deprecated Use editIntentAt instead
*/
+ @NonNull
@Override
@Deprecated
public Iterator<Intent> iterator() {
@@ -285,7 +289,7 @@
"No intents added to TaskStackBuilder; cannot startActivities");
}
- Intent[] intents = mIntents.toArray(new Intent[mIntents.size()]);
+ Intent[] intents = mIntents.toArray(new Intent[0]);
intents[0] = new Intent(intents[0]).addFlags(Intent.FLAG_ACTIVITY_NEW_TASK
| Intent.FLAG_ACTIVITY_CLEAR_TASK | Intent.FLAG_ACTIVITY_TASK_ON_HOME);
if (!ContextCompat.startActivities(mSourceContext, intents, options)) {
@@ -333,13 +337,12 @@
"No intents added to TaskStackBuilder; cannot getPendingIntent");
}
- Intent[] intents = mIntents.toArray(new Intent[mIntents.size()]);
+ Intent[] intents = mIntents.toArray(new Intent[0]);
intents[0] = new Intent(intents[0]).addFlags(Intent.FLAG_ACTIVITY_NEW_TASK
| Intent.FLAG_ACTIVITY_CLEAR_TASK | Intent.FLAG_ACTIVITY_TASK_ON_HOME);
if (Build.VERSION.SDK_INT >= 16) {
- return PendingIntent.getActivities(mSourceContext, requestCode, intents, flags,
- options);
+ return Api16Impl.getActivities(mSourceContext, requestCode, intents, flags, options);
} else {
return PendingIntent.getActivities(mSourceContext, requestCode, intents, flags);
}
@@ -364,4 +367,17 @@
}
return intents;
}
+
+ @RequiresApi(16)
+ static class Api16Impl {
+ private Api16Impl() {
+ // This class is not instantiable.
+ }
+
+ @DoNotInline
+ static PendingIntent getActivities(Context context, int requestCode, Intent[] intents,
+ int flags, Bundle options) {
+ return PendingIntent.getActivities(context, requestCode, intents, flags, options);
+ }
+ }
}
diff --git a/core/core/src/main/java/androidx/core/content/res/ResourcesCompat.java b/core/core/src/main/java/androidx/core/content/res/ResourcesCompat.java
index 07547c7..30b9a3d 100644
--- a/core/core/src/main/java/androidx/core/content/res/ResourcesCompat.java
+++ b/core/core/src/main/java/androidx/core/content/res/ResourcesCompat.java
@@ -69,6 +69,7 @@
/**
* Helper for accessing features in {@link Resources}.
*/
+@SuppressWarnings("unused")
public final class ResourcesCompat {
private static final String TAG = "ResourcesCompat";
private static final ThreadLocal<TypedValue> sTempTypedValue = new ThreadLocal<>();
@@ -274,7 +275,8 @@
final ColorStateListCacheEntry entry = entries.get(resId);
if (entry != null) {
if (entry.mConfiguration.equals(key.mResources.getConfiguration())
- && entry.mThemeHash == key.mTheme.hashCode()) {
+ && ((key.mTheme == null && entry.mThemeHash == 0)
+ || (key.mTheme != null && entry.mThemeHash == key.mTheme.hashCode()))) {
// If the current configuration matches the entry's, we can use it
return entry.mValue;
} else {
diff --git a/core/core/src/main/java/androidx/core/database/CursorWindowCompat.java b/core/core/src/main/java/androidx/core/database/CursorWindowCompat.java
index 01a48df..03b0c8f 100644
--- a/core/core/src/main/java/androidx/core/database/CursorWindowCompat.java
+++ b/core/core/src/main/java/androidx/core/database/CursorWindowCompat.java
@@ -19,16 +19,18 @@
import android.database.CursorWindow;
import android.os.Build;
+import androidx.annotation.DoNotInline;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
+import androidx.annotation.RequiresApi;
/**
- * Helper for accessing features in {@link android.database.CursorWindow}
+ * Helper for accessing features in {@link CursorWindow}
*/
public final class CursorWindowCompat {
private CursorWindowCompat() {
- /* Hide constructor */
+ // This class is not instantiable.
}
/**
@@ -40,11 +42,35 @@
@NonNull
public static CursorWindow create(@Nullable String name, long windowSizeBytes) {
if (Build.VERSION.SDK_INT >= 28) {
- return new CursorWindow(name, windowSizeBytes);
+ return Api28Impl.createCursorWindow(name, windowSizeBytes);
} else if (Build.VERSION.SDK_INT >= 15) {
- return new CursorWindow(name);
+ return Api15Impl.createCursorWindow(name);
} else {
return new CursorWindow(false);
}
}
+
+ @RequiresApi(28)
+ static class Api28Impl {
+ private Api28Impl() {
+ // This class is not instantiable.
+ }
+
+ @DoNotInline
+ static CursorWindow createCursorWindow(String name, long windowSizeBytes) {
+ return new CursorWindow(name, windowSizeBytes);
+ }
+ }
+
+ @RequiresApi(15)
+ static class Api15Impl {
+ private Api15Impl() {
+ // This class is not instantiable.
+ }
+
+ @DoNotInline
+ static CursorWindow createCursorWindow(String name) {
+ return new CursorWindow(name);
+ }
+ }
}
diff --git a/core/core/src/main/java/androidx/core/database/sqlite/SQLiteCursorCompat.java b/core/core/src/main/java/androidx/core/database/sqlite/SQLiteCursorCompat.java
index e46713c..1a2225e 100644
--- a/core/core/src/main/java/androidx/core/database/sqlite/SQLiteCursorCompat.java
+++ b/core/core/src/main/java/androidx/core/database/sqlite/SQLiteCursorCompat.java
@@ -16,18 +16,21 @@
package androidx.core.database.sqlite;
+import android.database.AbstractWindowedCursor;
import android.database.sqlite.SQLiteCursor;
import android.os.Build;
+import androidx.annotation.DoNotInline;
import androidx.annotation.NonNull;
+import androidx.annotation.RequiresApi;
/**
- * Helper for accessing features in {@link android.database.AbstractWindowedCursor}
+ * Helper for accessing features in {@link AbstractWindowedCursor}
*/
public final class SQLiteCursorCompat {
private SQLiteCursorCompat() {
- /* Hide constructor */
+ // This class is not instantiable.
}
/**
@@ -43,6 +46,18 @@
public static void setFillWindowForwardOnly(
@NonNull SQLiteCursor cursor, boolean fillWindowForwardOnly) {
if (Build.VERSION.SDK_INT >= 28) {
+ Api28Impl.setFillWindowForwardOnly(cursor, fillWindowForwardOnly);
+ }
+ }
+
+ @RequiresApi(28)
+ static class Api28Impl {
+ private Api28Impl() {
+ // This class is not instantiable.
+ }
+
+ @DoNotInline
+ static void setFillWindowForwardOnly(SQLiteCursor cursor, boolean fillWindowForwardOnly) {
cursor.setFillWindowForwardOnly(fillWindowForwardOnly);
}
}
diff --git a/core/core/src/main/java/androidx/core/graphics/BitmapCompat.java b/core/core/src/main/java/androidx/core/graphics/BitmapCompat.java
index 9902c00..aae8d3d 100644
--- a/core/core/src/main/java/androidx/core/graphics/BitmapCompat.java
+++ b/core/core/src/main/java/androidx/core/graphics/BitmapCompat.java
@@ -18,38 +18,110 @@
import android.graphics.Bitmap;
import android.os.Build;
+import androidx.annotation.DoNotInline;
import androidx.annotation.NonNull;
+import androidx.annotation.RequiresApi;
/**
- * Helper for accessing features in {@link android.graphics.Bitmap}.
+ * Helper for accessing features in {@link Bitmap}.
*/
public final class BitmapCompat {
+
+ /**
+ * Indicates whether the renderer responsible for drawing this
+ * bitmap should attempt to use mipmaps when this bitmap is drawn
+ * scaled down.
+ * <p>
+ * If you know that you are going to draw this bitmap at less than
+ * 50% of its original size, you may be able to obtain a higher
+ * quality
+ * <p>
+ * This property is only a suggestion that can be ignored by the
+ * renderer. It is not guaranteed to have any effect.
+ *
+ * @return true if the renderer should attempt to use mipmaps,
+ * false otherwise
+ *
+ * @see Bitmap#hasMipMap()
+ */
public static boolean hasMipMap(@NonNull Bitmap bitmap) {
- if (Build.VERSION.SDK_INT >= 18) {
- return bitmap.hasMipMap();
+ if (Build.VERSION.SDK_INT >= 17) {
+ return Api17Impl.hasMipMap(bitmap);
}
return false;
}
+ /**
+ * Set a hint for the renderer responsible for drawing this bitmap
+ * indicating that it should attempt to use mipmaps when this bitmap
+ * is drawn scaled down.
+ * <p>
+ * If you know that you are going to draw this bitmap at less than
+ * 50% of its original size, you may be able to obtain a higher
+ * quality by turning this property on.
+ * <p>
+ * Note that if the renderer respects this hint it might have to
+ * allocate extra memory to hold the mipmap levels for this bitmap.
+ * <p>
+ * This property is only a suggestion that can be ignored by the
+ * renderer. It is not guaranteed to have any effect.
+ *
+ * @param hasMipMap indicates whether the renderer should attempt
+ * to use mipmaps
+ *
+ * @see Bitmap#setHasMipMap(boolean)
+ *
+ */
public static void setHasMipMap(@NonNull Bitmap bitmap, boolean hasMipMap) {
- if (Build.VERSION.SDK_INT >= 18) {
- bitmap.setHasMipMap(hasMipMap);
+ if (Build.VERSION.SDK_INT >= 17) {
+ Api17Impl.setHasMipMap(bitmap, hasMipMap);
}
}
/**
- * Returns the size of the allocated memory used to store this bitmap's pixels in a backwards
- * compatible way.
+ * Returns the size of the allocated memory used to store this bitmap's pixels.
+ * <p>
+ * This value will not change over the lifetime of a Bitmap.
*
- * @param bitmap the bitmap in which to return its allocation size
- * @return the allocation size in bytes
+ * @see Bitmap#getAllocationByteCount()
*/
public static int getAllocationByteCount(@NonNull Bitmap bitmap) {
if (Build.VERSION.SDK_INT >= 19) {
- return bitmap.getAllocationByteCount();
+ return Api19Impl.getAllocationByteCount(bitmap);
}
return bitmap.getByteCount();
}
- private BitmapCompat() {}
+ private BitmapCompat() {
+ // This class is not instantiable.
+ }
+
+ @RequiresApi(17)
+ static class Api17Impl {
+ private Api17Impl() {
+ // This class is not instantiable.
+ }
+
+ @DoNotInline
+ static boolean hasMipMap(Bitmap bitmap) {
+ return bitmap.hasMipMap();
+ }
+
+ @DoNotInline
+ static void setHasMipMap(Bitmap bitmap, boolean hasMipMap) {
+ bitmap.setHasMipMap(hasMipMap);
+ }
+ }
+
+ @RequiresApi(19)
+ static class Api19Impl {
+ private Api19Impl() {
+ // This class is not instantiable.
+ }
+
+ @DoNotInline
+ static int getAllocationByteCount(Bitmap bitmap) {
+ return bitmap.getAllocationByteCount();
+ }
+ }
}
diff --git a/core/core/src/main/java/androidx/core/graphics/BlendModeColorFilterCompat.java b/core/core/src/main/java/androidx/core/graphics/BlendModeColorFilterCompat.java
index 1843c25..cff8d8f 100644
--- a/core/core/src/main/java/androidx/core/graphics/BlendModeColorFilterCompat.java
+++ b/core/core/src/main/java/androidx/core/graphics/BlendModeColorFilterCompat.java
@@ -23,8 +23,10 @@
import android.graphics.PorterDuffColorFilter;
import android.os.Build;
+import androidx.annotation.DoNotInline;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
+import androidx.annotation.RequiresApi;
/**
* Helper for accessing ColorFilter APIs on various API levels of the platform
@@ -32,17 +34,19 @@
public class BlendModeColorFilterCompat {
/**
- * Convenience method to create ColorFilter in a backward
- * compatible way. This method falls back on PorterDuffColorFilter for API levels that
- * do not support BlendModeColorFilter. This method returns null if the BlendMode provided is
- * not supported on a given API level.
+ * Convenience method to create ColorFilter in a backward-compatible way.
+ *
+ * This method falls back on PorterDuffColorFilter for API levels that do not support
+ * BlendModeColorFilter. This method returns {@code null} if the BlendMode provided is not
+ * supported on a given API level.
*/
public static @Nullable ColorFilter createBlendModeColorFilterCompat(int color,
@NonNull BlendModeCompat blendModeCompat) {
if (Build.VERSION.SDK_INT >= 29) {
- BlendMode blendMode = BlendModeUtils.obtainBlendModeFromCompat(blendModeCompat);
+ Object blendMode =
+ BlendModeUtils.Api29Impl.obtainBlendModeFromCompat(blendModeCompat);
return blendMode != null
- ? new BlendModeColorFilter(color, blendMode) : null;
+ ? Api29Impl.createBlendModeColorFilter(color, blendMode) : null;
} else {
PorterDuff.Mode porterDuffMode =
BlendModeUtils.obtainPorterDuffFromCompat(blendModeCompat);
@@ -51,5 +55,19 @@
}
}
- private BlendModeColorFilterCompat() { }
+ private BlendModeColorFilterCompat() {
+ // This class is not instantiable.
+ }
+
+ @RequiresApi(29)
+ static class Api29Impl {
+ private Api29Impl() {
+ // This class is not instantiable.
+ }
+
+ @DoNotInline
+ static ColorFilter createBlendModeColorFilter(int color, Object mode) {
+ return new BlendModeColorFilter(color, (BlendMode) mode);
+ }
+ }
}
diff --git a/core/core/src/main/java/androidx/core/graphics/BlendModeUtils.java b/core/core/src/main/java/androidx/core/graphics/BlendModeUtils.java
index a5188a5..feb339b 100644
--- a/core/core/src/main/java/androidx/core/graphics/BlendModeUtils.java
+++ b/core/core/src/main/java/androidx/core/graphics/BlendModeUtils.java
@@ -19,6 +19,7 @@
import android.graphics.BlendMode;
import android.graphics.PorterDuff;
+import androidx.annotation.DoNotInline;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.RequiresApi;
@@ -27,123 +28,131 @@
* Utility class used to map BlendModeCompat parameters to the corresponding
* PorterDuff mode or BlendMode depending on the API level of the platform
*/
-/* package */ class BlendModeUtils {
+class BlendModeUtils {
+ private BlendModeUtils() {
+ // This class is not instantiable.
+ }
@RequiresApi(29)
- /* package */ static @Nullable BlendMode obtainBlendModeFromCompat(
- @NonNull BlendModeCompat blendModeCompat) {
+ static class Api29Impl {
+ private Api29Impl() {
+ // This class is not instantiable.
+ }
+
+ @DoNotInline
+ @Nullable
+ static Object obtainBlendModeFromCompat(@NonNull BlendModeCompat blendModeCompat) {
+ switch (blendModeCompat) {
+ case CLEAR:
+ return BlendMode.CLEAR;
+ case SRC:
+ return BlendMode.SRC;
+ case DST:
+ return BlendMode.DST;
+ case SRC_OVER:
+ return BlendMode.SRC_OVER;
+ case DST_OVER:
+ return BlendMode.DST_OVER;
+ case SRC_IN:
+ return BlendMode.SRC_IN;
+ case DST_IN:
+ return BlendMode.DST_IN;
+ case SRC_OUT:
+ return BlendMode.SRC_OUT;
+ case DST_OUT:
+ return BlendMode.DST_OUT;
+ case SRC_ATOP:
+ return BlendMode.SRC_ATOP;
+ case DST_ATOP:
+ return BlendMode.DST_ATOP;
+ case XOR:
+ return BlendMode.XOR;
+ case PLUS:
+ return BlendMode.PLUS;
+ case MODULATE:
+ return BlendMode.MODULATE;
+ case SCREEN:
+ return BlendMode.SCREEN;
+ case OVERLAY:
+ return BlendMode.OVERLAY;
+ case DARKEN:
+ return BlendMode.DARKEN;
+ case LIGHTEN:
+ return BlendMode.LIGHTEN;
+ case COLOR_DODGE:
+ return BlendMode.COLOR_DODGE;
+ case COLOR_BURN:
+ return BlendMode.COLOR_BURN;
+ case HARD_LIGHT:
+ return BlendMode.HARD_LIGHT;
+ case SOFT_LIGHT:
+ return BlendMode.SOFT_LIGHT;
+ case DIFFERENCE:
+ return BlendMode.DIFFERENCE;
+ case EXCLUSION:
+ return BlendMode.EXCLUSION;
+ case MULTIPLY:
+ return BlendMode.MULTIPLY;
+ case HUE:
+ return BlendMode.HUE;
+ case SATURATION:
+ return BlendMode.SATURATION;
+ case COLOR:
+ return BlendMode.COLOR;
+ case LUMINOSITY:
+ return BlendMode.LUMINOSITY;
+ default:
+ return null;
+ }
+ }
+ }
+
+ static @Nullable PorterDuff.Mode obtainPorterDuffFromCompat(
+ @Nullable BlendModeCompat blendModeCompat) {
+ if (blendModeCompat == null) {
+ return null;
+ }
+
switch (blendModeCompat) {
case CLEAR:
- return BlendMode.CLEAR;
+ return PorterDuff.Mode.CLEAR;
case SRC:
- return BlendMode.SRC;
+ return PorterDuff.Mode.SRC;
case DST:
- return BlendMode.DST;
+ return PorterDuff.Mode.DST;
case SRC_OVER:
- return BlendMode.SRC_OVER;
+ return PorterDuff.Mode.SRC_OVER;
case DST_OVER:
- return BlendMode.DST_OVER;
+ return PorterDuff.Mode.DST_OVER;
case SRC_IN:
- return BlendMode.SRC_IN;
+ return PorterDuff.Mode.SRC_IN;
case DST_IN:
- return BlendMode.DST_IN;
+ return PorterDuff.Mode.DST_IN;
case SRC_OUT:
- return BlendMode.SRC_OUT;
+ return PorterDuff.Mode.SRC_OUT;
case DST_OUT:
- return BlendMode.DST_OUT;
+ return PorterDuff.Mode.DST_OUT;
case SRC_ATOP:
- return BlendMode.SRC_ATOP;
+ return PorterDuff.Mode.SRC_ATOP;
case DST_ATOP:
- return BlendMode.DST_ATOP;
+ return PorterDuff.Mode.DST_ATOP;
case XOR:
- return BlendMode.XOR;
+ return PorterDuff.Mode.XOR;
case PLUS:
- return BlendMode.PLUS;
+ return PorterDuff.Mode.ADD;
+ // b/73224934 PorterDuff Multiply maps to Skia Modulate
case MODULATE:
- return BlendMode.MODULATE;
+ return PorterDuff.Mode.MULTIPLY;
case SCREEN:
- return BlendMode.SCREEN;
+ return PorterDuff.Mode.SCREEN;
case OVERLAY:
- return BlendMode.OVERLAY;
+ return PorterDuff.Mode.OVERLAY;
case DARKEN:
- return BlendMode.DARKEN;
+ return PorterDuff.Mode.DARKEN;
case LIGHTEN:
- return BlendMode.LIGHTEN;
- case COLOR_DODGE:
- return BlendMode.COLOR_DODGE;
- case COLOR_BURN:
- return BlendMode.COLOR_BURN;
- case HARD_LIGHT:
- return BlendMode.HARD_LIGHT;
- case SOFT_LIGHT:
- return BlendMode.SOFT_LIGHT;
- case DIFFERENCE:
- return BlendMode.DIFFERENCE;
- case EXCLUSION:
- return BlendMode.EXCLUSION;
- case MULTIPLY:
- return BlendMode.MULTIPLY;
- case HUE:
- return BlendMode.HUE;
- case SATURATION:
- return BlendMode.SATURATION;
- case COLOR:
- return BlendMode.COLOR;
- case LUMINOSITY:
- return BlendMode.LUMINOSITY;
+ return PorterDuff.Mode.LIGHTEN;
default:
return null;
}
}
-
- /* package */ static @Nullable PorterDuff.Mode obtainPorterDuffFromCompat(
- @Nullable BlendModeCompat blendModeCompat) {
- if (blendModeCompat != null) {
- switch (blendModeCompat) {
- case CLEAR:
- return PorterDuff.Mode.CLEAR;
- case SRC:
- return PorterDuff.Mode.SRC;
- case DST:
- return PorterDuff.Mode.DST;
- case SRC_OVER:
- return PorterDuff.Mode.SRC_OVER;
- case DST_OVER:
- return PorterDuff.Mode.DST_OVER;
- case SRC_IN:
- return PorterDuff.Mode.SRC_IN;
- case DST_IN:
- return PorterDuff.Mode.DST_IN;
- case SRC_OUT:
- return PorterDuff.Mode.SRC_OUT;
- case DST_OUT:
- return PorterDuff.Mode.DST_OUT;
- case SRC_ATOP:
- return PorterDuff.Mode.SRC_ATOP;
- case DST_ATOP:
- return PorterDuff.Mode.DST_ATOP;
- case XOR:
- return PorterDuff.Mode.XOR;
- case PLUS:
- return PorterDuff.Mode.ADD;
- // b/73224934 PorterDuff Multiply maps to Skia Modulate
- case MODULATE:
- return PorterDuff.Mode.MULTIPLY;
- case SCREEN:
- return PorterDuff.Mode.SCREEN;
- case OVERLAY:
- return PorterDuff.Mode.OVERLAY;
- case DARKEN:
- return PorterDuff.Mode.DARKEN;
- case LIGHTEN:
- return PorterDuff.Mode.LIGHTEN;
- default:
- return null;
- }
- } else {
- return null;
- }
- }
-
- private BlendModeUtils() { }
}
diff --git a/core/core/src/main/java/androidx/core/graphics/PaintCompat.java b/core/core/src/main/java/androidx/core/graphics/PaintCompat.java
index 73862de..4503d2c 100644
--- a/core/core/src/main/java/androidx/core/graphics/PaintCompat.java
+++ b/core/core/src/main/java/androidx/core/graphics/PaintCompat.java
@@ -16,7 +16,6 @@
package androidx.core.graphics;
-import static androidx.core.graphics.BlendModeUtils.obtainBlendModeFromCompat;
import static androidx.core.graphics.BlendModeUtils.obtainPorterDuffFromCompat;
import android.graphics.BlendMode;
@@ -127,8 +126,9 @@
*/
public static boolean setBlendMode(@NonNull Paint paint, @Nullable BlendModeCompat blendMode) {
if (Build.VERSION.SDK_INT >= 29) {
- Api29Impl.setBlendMode(paint,
- blendMode != null ? obtainBlendModeFromCompat(blendMode) : null);
+ Object blendModePlatform = blendMode != null
+ ? BlendModeUtils.Api29Impl.obtainBlendModeFromCompat(blendMode) : null;
+ Api29Impl.setBlendMode(paint, blendModePlatform);
// All blend modes supported in Q
return true;
} else if (blendMode != null) {
@@ -160,6 +160,18 @@
private PaintCompat() {
}
+ @RequiresApi(29)
+ static class Api29Impl {
+ private Api29Impl() {
+ // This class is not instantiable.
+ }
+
+ @DoNotInline
+ static void setBlendMode(Paint paint, Object blendmode) {
+ paint.setBlendMode((BlendMode) blendmode);
+ }
+ }
+
@RequiresApi(23)
static class Api23Impl {
private Api23Impl() {
@@ -171,16 +183,4 @@
return paint.hasGlyph(string);
}
}
-
- @RequiresApi(29)
- static class Api29Impl {
- private Api29Impl() {
- // This class is not instantiable.
- }
-
- @DoNotInline
- static void setBlendMode(Paint paint, BlendMode blendmode) {
- paint.setBlendMode(blendmode);
- }
- }
}
diff --git a/docs-tip-of-tree/build.gradle b/docs-tip-of-tree/build.gradle
index 14f904f..a6333bf 100644
--- a/docs-tip-of-tree/build.gradle
+++ b/docs-tip-of-tree/build.gradle
@@ -102,6 +102,7 @@
docs(project(":core:core-animation-testing"))
docs(project(":core:core-appdigest"))
docs(project(":core:core-google-shortcuts"))
+ docs(project(":core:core-i18n"))
docs(project(":core:core-ktx"))
docs(project(":core:core-performance"))
samples(project(":core:core-performance:core-performance-samples"))
diff --git a/docs/api_guidelines.md b/docs/api_guidelines.md
index c60844cd..e732660 100644
--- a/docs/api_guidelines.md
+++ b/docs/api_guidelines.md
@@ -153,13 +153,13 @@
library at the same time and release all libraries at the same time.
Atomic groups are specified in
-[`LibraryGroups.kt`](https://cs.android.com/androidx/platform/frameworks/support/+/androidx-main:buildSrc/public/src/main/kotlin/androidx/build/LibraryGroups.kt):
+[libraryversions.toml](https://cs.android.com/androidx/platform/frameworks/support/+/androidx-main:libraryversions.toml):
-```kotlin
+```
// Non-atomic library group
-val APPCOMPAT = LibraryGroup("androidx.appcompat", null)
+APPCOMPAT = { group = "androidx.appcompat" }
// Atomic library group
-val APPSEARCH = LibraryGroup("androidx.appsearch", LibraryVersions.APPSEARCH)
+APPSEARCH = { group = "androidx.appsearch", atomicGroupVersion = "versions.APPSEARCH" }
```
Libraries within an atomic group should not specify a version in their
diff --git a/docs/benchmarking.md b/docs/benchmarking.md
index c54786a..f138231 100644
--- a/docs/benchmarking.md
+++ b/docs/benchmarking.md
@@ -76,25 +76,46 @@
### I'm lazy and want to start quickly
-Start by copying one of the following projects:
+Start by copying one of the following non-Compose projects:
* [navigation-benchmark](https://android.googlesource.com/platform/frameworks/support/+/refs/heads/androidx-main/navigation/benchmark/)
* [recyclerview-benchmark](https://android.googlesource.com/platform/frameworks/support/+/refs/heads/androidx-main/recyclerview/recyclerview-benchmark/)
-### Compose
+Many Compose libraries already have benchmark modules:
-Compose builds the benchmark from source, so usage matches the rest of the
-AndroidX project. See existing Compose benchmark projects:
-
-* [Compose UI benchmarks](https://cs.android.com/androidx/platform/frameworks/support/+/androidx-main:compose/integration-tests/benchmark/)
-* [Compose Runtime benchmarks](https://cs.android.com/androidx/platform/frameworks/support/+/androidx-main:compose/runtime/runtime/compose-runtime-benchmark/)
+* [Compose UI Benchmarks](https://cs.android.com/androidx/platform/frameworks/support/+/androidx-main:compose/ui/ui/benchmark/)
+* [Compose Runtime Benchmarks](https://cs.android.com/androidx/platform/frameworks/support/+/androidx-main:compose/runtime/runtime/compose-runtime-benchmark/)
+* [Compose Material Benchmarks](https://cs.android.com/androidx/platform/frameworks/support/+/androidx-main:compose/material/material/benchmark/)
+* [Wear Compose Material Benchmarks](https://cs.android.com/androidx/platform/frameworks/support/+/androidx-main:wear/compose/compose-material/benchmark/)
## Profiling
-### Command Line
+See the
+[public profiling guide](https://developer.android.com/studio/profile/benchmark#profiling)
+for more details.
-The benchmark library supports capturing profiling information - stack sampling
-and method tracing - from the command line. Here's an example which runs the
+Jetpack benchmark supports capturing profiling information by setting
+instrumentation arguments. Stack sampling and method tracing can be performed
+either from CLI or Studio invocation.
+
+### Set Arguments in Gradle
+
+Args can be set in your benchmark's `build.gradle`, which will affect both
+Studio / command-line gradlew runs. Runs from Studio will link result traces
+that can be opened directly from the IDE.
+
+```
+android {
+ defaultConfig {
+ // must be one of: 'None', 'StackSampling', or 'MethodTracing'
+ testInstrumentationRunnerArgument 'androidx.benchmark.profiling.mode', 'StackSampling'
+ }
+}
+```
+
+### Set Arguments on Command Line
+
+Args can also be passed from CLI. Here's an example which runs the
`androidx.compose.material.benchmark.CheckboxesInRowsBenchmark#draw` method with
`StackSampling` profiling:
@@ -130,16 +151,15 @@
![Sample flame chart](benchmarking_images/profiling_flame_chart.png "Sample flame chart")
-NOTE Simpleperf captures stack traces from all threads, so click the test thread
-in the left profiler panel, and select flame chart on the right to see just
-samples from the test.
+### Advanced: Connected Studio Profiler
-### Advanced: Studio Profiling
+Profiling for allocations requires Studio to capture. This can also be used for
+Sampled profiling, though it is instead recommended to use instrumentation
+argument profiling for that, as it's simpler, and doesn't require
+`debuggable=true`
-Profiling for allocations and simpleperf profiling requires Studio to capture.
-
-Studio profiling tools require `debuggable=true`. First, temporarily override it
-in your benchmark's `androidTest/AndroidManifest.xml`.
+Studio profiling tools currently require `debuggable=true`. First, temporarily
+override it in your benchmark's `androidTest/AndroidManifest.xml`.
Next choose which profiling you want to do: Allocation, or Sampled (SimplePerf)
diff --git a/docs/lint_guide.md b/docs/lint_guide.md
index a3fc49f..9a0fdb7 100644
--- a/docs/lint_guide.md
+++ b/docs/lint_guide.md
@@ -446,15 +446,19 @@
```kotlin
override fun visitElement(context: XmlContext, element: Element) {
- context.report(
- ISSUE,
- context.getNameLocation(element),
- "My issue message",
- fix().replace()
- .text(ELEMENT)
- .with(REPLACEMENT TEXT)
- .build()
- )
+ val lintFix = fix().replace()
+ .text(ELEMENT)
+ .with(REPLACEMENT TEXT)
+ .build()
+
+ val incident = Incident(context)
+ .fix(lintFix)
+ .issue(ISSUE)
+ .location(context.getLocation(node))
+ .message("My issue message")
+ .scope(context.getNameLocation(element))
+
+ context.report(incident)
}
```
diff --git a/docs/versioning.md b/docs/versioning.md
index 88250d7..42bec31 100644
--- a/docs/versioning.md
+++ b/docs/versioning.md
@@ -339,8 +339,7 @@
### How to update your version
-1. Update the version listed in
- `frameworks/support/buildSrc/public/src/main/kotlin/androidx/build/LibraryVersions.kt`
+1. Update the version listed in `frameworks/support/libraryversions.toml`
1. If your library is a `beta` or `rc01` version, run `./gradlew
<your-lib>:updateApi`. This will create an API txt file for the new version
of your library. For other versions, this step is not reqired
diff --git a/glance/glance-appwidget/integration-tests/demos/src/main/AndroidManifest.xml b/glance/glance-appwidget/integration-tests/demos/src/main/AndroidManifest.xml
index 8185c92..6e0b227 100644
--- a/glance/glance-appwidget/integration-tests/demos/src/main/AndroidManifest.xml
+++ b/glance/glance-appwidget/integration-tests/demos/src/main/AndroidManifest.xml
@@ -186,5 +186,18 @@
android:name="android.appwidget.provider"
android:resource="@xml/default_app_widget_info" />
</receiver>
+
+ <receiver
+ android:name="androidx.glance.appwidget.demos.ProgressIndicatorAppWidgetReceiver"
+ android:label="@string/progress_indicator_widget_name"
+ android:enabled="@bool/glance_appwidget_available"
+ android:exported="false">
+ <intent-filter>
+ <action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
+ </intent-filter>
+ <meta-data
+ android:name="android.appwidget.provider"
+ android:resource="@xml/default_app_widget_info" />
+ </receiver>
</application>
</manifest>
diff --git a/glance/glance-appwidget/integration-tests/demos/src/main/java/androidx/glance/appwidget/demos/ProgressIndicatorAppWidget.kt b/glance/glance-appwidget/integration-tests/demos/src/main/java/androidx/glance/appwidget/demos/ProgressIndicatorAppWidget.kt
new file mode 100644
index 0000000..ed6e9b7
--- /dev/null
+++ b/glance/glance-appwidget/integration-tests/demos/src/main/java/androidx/glance/appwidget/demos/ProgressIndicatorAppWidget.kt
@@ -0,0 +1,53 @@
+/*
+ * Copyright 2022 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.glance.appwidget.demos
+
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.unit.dp
+import androidx.glance.GlanceModifier
+import androidx.glance.appwidget.CircularProgressIndicator
+import androidx.glance.appwidget.GlanceAppWidget
+import androidx.glance.appwidget.GlanceAppWidgetReceiver
+import androidx.glance.appwidget.LinearProgressIndicator
+import androidx.glance.background
+import androidx.glance.layout.Alignment
+import androidx.glance.layout.Column
+import androidx.glance.layout.Spacer
+import androidx.glance.layout.fillMaxSize
+import androidx.glance.layout.size
+
+class ProgressIndicatorAppWidget : GlanceAppWidget() {
+
+ @Composable
+ override fun Content() {
+ Column(
+ modifier = GlanceModifier.fillMaxSize().background(R.color.default_widget_background),
+ verticalAlignment = Alignment.CenterVertically,
+ horizontalAlignment = Alignment.CenterHorizontally
+ ) {
+ LinearProgressIndicator()
+ Spacer(GlanceModifier.size(8.dp))
+ LinearProgressIndicator(0.5f)
+ Spacer(GlanceModifier.size(8.dp))
+ CircularProgressIndicator()
+ }
+ }
+}
+
+class ProgressIndicatorAppWidgetReceiver : GlanceAppWidgetReceiver() {
+ override val glanceAppWidget: GlanceAppWidget = ProgressIndicatorAppWidget()
+}
\ No newline at end of file
diff --git a/glance/glance-appwidget/integration-tests/demos/src/main/java/androidx/glance/appwidget/demos/ScrollableAppWidget.kt b/glance/glance-appwidget/integration-tests/demos/src/main/java/androidx/glance/appwidget/demos/ScrollableAppWidget.kt
index d66af66..7de3980 100644
--- a/glance/glance-appwidget/integration-tests/demos/src/main/java/androidx/glance/appwidget/demos/ScrollableAppWidget.kt
+++ b/glance/glance-appwidget/integration-tests/demos/src/main/java/androidx/glance/appwidget/demos/ScrollableAppWidget.kt
@@ -39,16 +39,15 @@
import androidx.glance.action.actionParametersOf
import androidx.glance.action.clickable
import androidx.glance.appwidget.CheckBox
-import androidx.glance.appwidget.CircularProgressIndicator
import androidx.glance.appwidget.GlanceAppWidget
import androidx.glance.appwidget.GlanceAppWidgetReceiver
-import androidx.glance.appwidget.LinearProgressIndicator
import androidx.glance.appwidget.SizeMode
import androidx.glance.appwidget.action.ActionCallback
import androidx.glance.appwidget.action.ToggleableStateKey
import androidx.glance.appwidget.action.actionRunCallback
import androidx.glance.appwidget.action.actionStartActivity
import androidx.glance.appwidget.demos.ScrollableAppWidget.Companion.CheckboxKey
+import androidx.glance.appwidget.lazy.GridCells
import androidx.glance.appwidget.lazy.LazyColumn
import androidx.glance.appwidget.lazy.itemsIndexed
import androidx.glance.appwidget.state.updateAppWidgetState
@@ -89,9 +88,6 @@
Column(
modifier = GlanceModifier.fillMaxSize().background(R.color.default_widget_background)
) {
- LinearProgressIndicator()
- LinearProgressIndicator(0.5f)
- CircularProgressIndicator()
Text(
text = "Fix header",
modifier = GlanceModifier
@@ -100,16 +96,14 @@
.background(Color(0x0a000000))
)
val width = LocalSize.current.width
- if (width <= singleColumn.width) {
- ScrollColumn(GlanceModifier.fillMaxSize())
- } else {
- Row {
+ when {
+ width <= singleColumn.width -> ScrollColumn(GlanceModifier.fillMaxSize())
+ width <= doubleColumn.width -> Row {
val modifier = GlanceModifier.fillMaxHeight().defaultWeight()
ScrollColumn(modifier)
- if (width >= tripleColumn.width) {
- ScrollColumn(modifier)
- }
+ ScrollColumn(modifier)
}
+ else -> SampleGrid(cells = GridCells.Fixed(3))
}
}
}
diff --git a/glance/glance-appwidget/integration-tests/demos/src/main/java/androidx/glance/appwidget/demos/VerticalGridAppWidget.kt b/glance/glance-appwidget/integration-tests/demos/src/main/java/androidx/glance/appwidget/demos/VerticalGridAppWidget.kt
index 8ae60bc..2f8de3f 100644
--- a/glance/glance-appwidget/integration-tests/demos/src/main/java/androidx/glance/appwidget/demos/VerticalGridAppWidget.kt
+++ b/glance/glance-appwidget/integration-tests/demos/src/main/java/androidx/glance/appwidget/demos/VerticalGridAppWidget.kt
@@ -17,72 +17,74 @@
package androidx.glance.appwidget.demos
import android.content.Intent
-import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.runtime.Composable
import androidx.glance.Button
import androidx.glance.GlanceModifier
import androidx.glance.LocalContext
-import androidx.glance.action.actionStartActivity
import androidx.glance.appwidget.GlanceAppWidget
import androidx.glance.appwidget.GlanceAppWidgetReceiver
import androidx.glance.appwidget.action.actionStartActivity
import androidx.glance.appwidget.appWidgetBackground
import androidx.glance.appwidget.cornerRadius
-import androidx.glance.appwidget.lazy.LazyVerticalGrid
import androidx.glance.appwidget.lazy.GridCells
+import androidx.glance.appwidget.lazy.LazyVerticalGrid
import androidx.glance.appwidget.lazy.itemsIndexed
+import androidx.glance.background
import androidx.glance.layout.Alignment
-import androidx.glance.layout.Column
import androidx.glance.layout.Row
-import androidx.glance.layout.fillMaxHeight
import androidx.glance.layout.fillMaxSize
import androidx.glance.layout.fillMaxWidth
import androidx.glance.layout.padding
-import androidx.glance.state.PreferencesGlanceStateDefinition
import androidx.glance.text.Text
class VerticalGridAppWidget : GlanceAppWidget() {
- override val stateDefinition = PreferencesGlanceStateDefinition
+ @Composable
+ override fun Content() {
+ SampleGrid(
+ cells = GridCells.Adaptive,
+ modifier = GlanceModifier.padding(R.dimen.external_padding)
+ .fillMaxSize()
+ .appWidgetBackground()
+ .cornerRadius(R.dimen.corner_radius)
+ .background(R.color.default_widget_background)
+ )
+ }
+}
- @Composable
- override fun Content() {
- Column(
- modifier = GlanceModifier.padding(R.dimen.external_padding).fillMaxSize()
- .appWidgetBackground().cornerRadius(R.dimen.corner_radius),
- verticalAlignment = Alignment.Vertical.CenterVertically,
- horizontalAlignment = Alignment.Horizontal.CenterHorizontally
- ) {
- val modifier = GlanceModifier.fillMaxHeight().defaultWeight()
- LazyVerticalGrid(gridCells = GridCells.Fixed(2), modifier) {
- item { Text("LazyVerticalGrid") }
- items(2, { it * 2L }) { index -> Text("Item $index") }
- itemsIndexed(
+@Composable
+fun SampleGrid(cells: GridCells, modifier: GlanceModifier = GlanceModifier.fillMaxSize()) {
+ LazyVerticalGrid(
+ modifier = modifier,
+ gridCells = cells
+ ) {
+ item {
+ Text("LazyVerticalGrid")
+ }
+ items(count = 20, itemId = { it * 2L }) { index ->
+ Text("Item $index")
+ }
+ itemsIndexed(
listOf(
GlanceAppWidgetDemoActivity::class.java,
ListClickDestinationActivity::class.java
)
) { index, activityClass ->
Row(
- GlanceModifier.fillMaxWidth(),
+ modifier = GlanceModifier.fillMaxWidth(),
horizontalAlignment = Alignment.Horizontal.CenterHorizontally
) {
Button(
text = "Activity ${index + 1}",
onClick = actionStartActivity(
- Intent(
- LocalContext.current,
- activityClass
- )
+ Intent(LocalContext.current, activityClass)
)
)
}
}
- }
}
- }
}
class VerticalGridAppWidgetReceiver : GlanceAppWidgetReceiver() {
- override val glanceAppWidget: GlanceAppWidget = VerticalGridAppWidget()
+ override val glanceAppWidget: GlanceAppWidget = VerticalGridAppWidget()
}
\ No newline at end of file
diff --git a/glance/glance-appwidget/integration-tests/demos/src/main/res/values/strings.xml b/glance/glance-appwidget/integration-tests/demos/src/main/res/values/strings.xml
index 3820d2b..ce122d4 100644
--- a/glance/glance-appwidget/integration-tests/demos/src/main/res/values/strings.xml
+++ b/glance/glance-appwidget/integration-tests/demos/src/main/res/values/strings.xml
@@ -31,4 +31,5 @@
<string name="image_widget_name">Image Widget</string>
<string name="grid_widget_name">Vertical Grid Widget</string>
<string name="default_state_widget_name">Default State Widget</string>
+ <string name="progress_indicator_widget_name">ProgressBar Widget</string>
</resources>
diff --git a/health/health-data-client/OWNERS b/health/health-data-client/OWNERS
index 851a73b..e66c7f0 100644
--- a/health/health-data-client/OWNERS
+++ b/health/health-data-client/OWNERS
@@ -1,2 +1,4 @@
hengruicao@google.com
jstembridge@google.com
+nickelc@google.com
+itsleo@google.com
\ No newline at end of file
diff --git a/libraryversions.toml b/libraryversions.toml
index 94a08ed..c0c5a51 100644
--- a/libraryversions.toml
+++ b/libraryversions.toml
@@ -27,6 +27,7 @@
CORE_ANIMATION_TESTING = "1.0.0-alpha03"
CORE_APPDIGEST = "1.0.0-alpha01"
CORE_GOOGLE_SHORTCUTS = "1.1.0-alpha02"
+CORE_I18N = "1.0.0-alpha01"
CORE_PERFORMANCE = "1.0.0-alpha02"
CORE_REMOTEVIEWS = "1.0.0-alpha03"
CORE_ROLE = "1.2.0-alpha01"
diff --git a/lint-checks/integration-tests/src/main/java/androidx/sample/consumer/OutsideGroupExperimentalAnnotatedClass.kt b/lint-checks/integration-tests/src/main/java/androidx/sample/consumer/OutsideGroupExperimentalAnnotatedClass.kt
index d6c1fee..278b5eb 100644
--- a/lint-checks/integration-tests/src/main/java/androidx/sample/consumer/OutsideGroupExperimentalAnnotatedClass.kt
+++ b/lint-checks/integration-tests/src/main/java/androidx/sample/consumer/OutsideGroupExperimentalAnnotatedClass.kt
@@ -20,6 +20,7 @@
import sample.annotation.provider.ExperimentalSampleAnnotationJava
import sample.annotation.provider.RequiresOptInSampleAnnotationJava
+import sample.annotation.provider.RequiresOptInSampleAnnotationJavaDuplicate
class OutsideGroupExperimentalAnnotatedClass {
@@ -38,4 +39,24 @@
fun invalidRequiresOptInAnnotatedMethod() {
// Nothing to see here.
}
+
+ @OptIn(RequiresOptInSampleAnnotationJava::class)
+ fun invalidMethodWithSingleOptIn() {
+ // Nothing to see here.
+ }
+
+ @OptIn(
+ RequiresOptInSampleAnnotationJava::class,
+ RequiresOptInSampleAnnotationJavaDuplicate::class
+ )
+ fun invalidMethodWithMultipleOptInsWithLineBreaks() {
+ // Nothing to see here.
+ }
+
+ /* ktlint-disable max-line-length */
+ @OptIn(RequiresOptInSampleAnnotationJava::class, RequiresOptInSampleAnnotationJavaDuplicate::class)
+ fun invalidMethodWithMultipleOptInsWithoutLineBreaks() {
+ // Nothing to see here.
+ }
+ /* ktlint-enable max-line-length */
}
diff --git a/lint-checks/integration-tests/src/main/java/sample/annotation/provider/RequiresOptInSampleAnnotationJavaDuplicate.java b/lint-checks/integration-tests/src/main/java/sample/annotation/provider/RequiresOptInSampleAnnotationJavaDuplicate.java
new file mode 100644
index 0000000..3d7b8f3
--- /dev/null
+++ b/lint-checks/integration-tests/src/main/java/sample/annotation/provider/RequiresOptInSampleAnnotationJavaDuplicate.java
@@ -0,0 +1,32 @@
+/*
+ * Copyright 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package sample.annotation.provider;
+
+import static java.lang.annotation.RetentionPolicy.CLASS;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.Target;
+
+import kotlin.RequiresOptIn;
+
+// This is essentially a duplicate of RequiresOptInSampleAnnotationJava. Combined, these two are
+// used in @OptIn with multiple @RequiresOptIn declarations.
+@RequiresOptIn
+@Retention(CLASS)
+@Target({ElementType.TYPE, ElementType.METHOD})
+public @interface RequiresOptInSampleAnnotationJavaDuplicate {}
diff --git a/lint-checks/src/main/java/androidx/build/lint/BanConcurrentHashMap.kt b/lint-checks/src/main/java/androidx/build/lint/BanConcurrentHashMap.kt
index 7aa81c8..b5529a3 100644
--- a/lint-checks/src/main/java/androidx/build/lint/BanConcurrentHashMap.kt
+++ b/lint-checks/src/main/java/androidx/build/lint/BanConcurrentHashMap.kt
@@ -21,6 +21,7 @@
import com.android.tools.lint.detector.api.Category
import com.android.tools.lint.detector.api.Detector
import com.android.tools.lint.detector.api.Implementation
+import com.android.tools.lint.detector.api.Incident
import com.android.tools.lint.detector.api.Issue
import com.android.tools.lint.detector.api.JavaContext
import com.android.tools.lint.detector.api.Scope
@@ -48,11 +49,12 @@
if (node.selector is USimpleNameReferenceExpression) {
val name = node.selector as USimpleNameReferenceExpression
if (CONCURRENT_HASHMAP == name.identifier) {
- context.report(
- ISSUE, node, context.getLocation(node),
- "Detected " +
- "ConcurrentHashMap usage."
- )
+ val incident = Incident(context)
+ .issue(ISSUE)
+ .location(context.getLocation(node))
+ .message("Detected ConcurrentHashMap usage.")
+ .scope(node)
+ context.report(incident)
}
}
}
@@ -69,11 +71,12 @@
if (resolved is PsiClass &&
CONCURRENT_HASHMAP_QUALIFIED_NAME == resolved.qualifiedName
) {
- context.report(
- ISSUE, node, context.getLocation(node),
- "Detected " +
- "ConcurrentHashMap usage."
- )
+ val incident = Incident(context)
+ .issue(ISSUE)
+ .location(context.getLocation(node))
+ .message("Detected ConcurrentHashMap usage.")
+ .scope(node)
+ context.report(incident)
}
}
}
diff --git a/lint-checks/src/main/java/androidx/build/lint/BanInappropriateExperimentalUsage.kt b/lint-checks/src/main/java/androidx/build/lint/BanInappropriateExperimentalUsage.kt
index a09f735..2c9da29 100644
--- a/lint-checks/src/main/java/androidx/build/lint/BanInappropriateExperimentalUsage.kt
+++ b/lint-checks/src/main/java/androidx/build/lint/BanInappropriateExperimentalUsage.kt
@@ -31,8 +31,12 @@
import org.jetbrains.uast.UAnnotated
import org.jetbrains.uast.UAnnotation
import org.jetbrains.uast.UClass
+import org.jetbrains.uast.UClassLiteralExpression
import org.jetbrains.uast.UElement
+import org.jetbrains.uast.UExpression
+import org.jetbrains.uast.kotlin.KotlinUVarargExpression
import org.jetbrains.uast.resolveToUElement
+import org.jetbrains.uast.toUElement
/**
* Prevents usage of experimental annotations outside the groups in which they were defined.
@@ -49,8 +53,10 @@
val atomicGroupList: List<String> by lazy { loadAtomicLibraryGroupList() }
override fun visitAnnotation(node: UAnnotation) {
+ val signature = node.qualifiedName
+
if (DEBUG) {
- if (APPLICABLE_ANNOTATIONS.contains(node.qualifiedName) && node.sourcePsi != null) {
+ if (APPLICABLE_ANNOTATIONS.contains(signature) && node.sourcePsi != null) {
(node.uastParent as? UClass)?.let { annotation ->
println(
"${context.driver.mode}: declared ${annotation.qualifiedName} in " +
@@ -60,8 +66,65 @@
}
}
- // If we find an usage of an experimentally-declared annotation, check it.
- val annotation = node.resolveToUElement()
+ /**
+ * If the annotation under evaluation is [kotlin.OptIn], extract and evaluate the
+ * annotation(s) referenced by @OptIn - denoted by [kotlin.OptIn.markerClass].
+ */
+ if (signature != null && signature == KOTLIN_OPT_IN_ANNOTATION) {
+ if (DEBUG) {
+ println("Processing $KOTLIN_OPT_IN_ANNOTATION annotation")
+ }
+
+ val markerClass: UExpression? = node.findAttributeValue("markerClass")
+ if (markerClass != null) {
+ getUElementsFromOptInMarkerClass(markerClass).forEach { uElement ->
+ inspectAnnotation(uElement, node)
+ }
+ }
+
+ /**
+ * [kotlin.OptIn] has no effect if [kotlin.OptIn.markerClass] isn't provided.
+ * Similarly, if [getUElementsFromOptInMarkerClass] returns an empty list then
+ * there isn't anything more to inspect.
+ *
+ * In both of these cases we can stop processing here.
+ */
+ return
+ }
+
+ inspectAnnotation(node.resolveToUElement(), node)
+ }
+
+ private fun getUElementsFromOptInMarkerClass(markerClass: UExpression): List<UElement> {
+ val elements = ArrayList<UElement?>()
+
+ when (markerClass) {
+ is UClassLiteralExpression -> { // opting in to single annotation
+ elements.add(markerClass.toUElement())
+ }
+ is KotlinUVarargExpression -> { // opting in to multiple annotations
+ val expressions: List<UExpression> = markerClass.valueArguments
+ for (expression in expressions) {
+ val uElement = (expression as UClassLiteralExpression).toUElement()
+ elements.add(uElement)
+ }
+ }
+ else -> {
+ // do nothing
+ }
+ }
+
+ return elements.filterNotNull()
+ }
+
+ private fun UClassLiteralExpression.toUElement(): UElement? {
+ val psiType = this.type
+ val psiClass = context.evaluator.getTypeClass(psiType)
+ return psiClass.toUElement()
+ }
+
+ // If we find an usage of an experimentally-declared annotation, check it.
+ private fun inspectAnnotation(annotation: UElement?, node: UAnnotation) {
if (annotation is UAnnotated) {
val annotations = context.evaluator.getAllAnnotations(annotation, false)
if (annotations.any { APPLICABLE_ANNOTATIONS.contains(it.qualifiedName) }) {
@@ -140,6 +203,7 @@
*/
private const val KOTLIN_EXPERIMENTAL_ANNOTATION = "kotlin.Experimental"
+ private const val KOTLIN_OPT_IN_ANNOTATION = "kotlin.OptIn"
private const val KOTLIN_REQUIRES_OPT_IN_ANNOTATION = "kotlin.RequiresOptIn"
private const val JAVA_EXPERIMENTAL_ANNOTATION =
"androidx.annotation.experimental.Experimental"
@@ -171,4 +235,4 @@
),
)
}
-}
\ No newline at end of file
+}
diff --git a/lint-checks/src/main/java/androidx/build/lint/BanKeepAnnotation.kt b/lint-checks/src/main/java/androidx/build/lint/BanKeepAnnotation.kt
index c2e6106..8bcfd3a 100644
--- a/lint-checks/src/main/java/androidx/build/lint/BanKeepAnnotation.kt
+++ b/lint-checks/src/main/java/androidx/build/lint/BanKeepAnnotation.kt
@@ -23,6 +23,7 @@
import com.android.tools.lint.detector.api.Category
import com.android.tools.lint.detector.api.Detector
import com.android.tools.lint.detector.api.Implementation
+import com.android.tools.lint.detector.api.Incident
import com.android.tools.lint.detector.api.Issue
import com.android.tools.lint.detector.api.JavaContext
import com.android.tools.lint.detector.api.Scope
@@ -42,10 +43,12 @@
if (node.qualifiedName == "androidx.annotation.Keep" ||
node.qualifiedName == "android.support.annotation.keep"
) {
- context.report(
- ISSUE, node, context.getNameLocation(node),
- "Uses @Keep annotation"
- )
+ val incident = Incident(context)
+ .issue(ISSUE)
+ .location(context.getNameLocation(node))
+ .message("Uses @Keep annotation")
+ .scope(node)
+ context.report(incident)
}
}
}
diff --git a/lint-checks/src/main/java/androidx/build/lint/BanParcelableUsage.kt b/lint-checks/src/main/java/androidx/build/lint/BanParcelableUsage.kt
index 08586eb..ff4de6a 100644
--- a/lint-checks/src/main/java/androidx/build/lint/BanParcelableUsage.kt
+++ b/lint-checks/src/main/java/androidx/build/lint/BanParcelableUsage.kt
@@ -21,6 +21,7 @@
import com.android.tools.lint.detector.api.Category
import com.android.tools.lint.detector.api.Detector
import com.android.tools.lint.detector.api.Implementation
+import com.android.tools.lint.detector.api.Incident
import com.android.tools.lint.detector.api.Issue
import com.android.tools.lint.detector.api.JavaContext
import com.android.tools.lint.detector.api.Scope
@@ -49,10 +50,12 @@
// lint will also examine the entire inheritance and implementation chain.
for (superclass in declaration.uastSuperTypes) {
if (superclass.type.canonicalText == PARCELABLE_INTERFACE_CANONICAL_NAME) {
- context.report(
- ISSUE, declaration, context.getNameLocation(declaration),
- "Class implements android.os.Parcelable"
- )
+ val incident = Incident(context)
+ .issue(ISSUE)
+ .location(context.getNameLocation(declaration))
+ .message("Class implements android.os.Parcelable")
+ .scope(declaration)
+ context.report(incident)
}
}
}
diff --git a/lint-checks/src/main/java/androidx/build/lint/BanSynchronizedMethods.kt b/lint-checks/src/main/java/androidx/build/lint/BanSynchronizedMethods.kt
index e1755ef1..7f50e3d 100644
--- a/lint-checks/src/main/java/androidx/build/lint/BanSynchronizedMethods.kt
+++ b/lint-checks/src/main/java/androidx/build/lint/BanSynchronizedMethods.kt
@@ -22,6 +22,7 @@
import com.android.tools.lint.detector.api.Category
import com.android.tools.lint.detector.api.Detector
import com.android.tools.lint.detector.api.Implementation
+import com.android.tools.lint.detector.api.Incident
import com.android.tools.lint.detector.api.Issue
import com.android.tools.lint.detector.api.JavaContext
import com.android.tools.lint.detector.api.Scope
@@ -36,12 +37,13 @@
override fun createUastHandler(context: JavaContext) = object : UElementHandler() {
override fun visitMethod(node: UMethod) {
if (node.hasModifier(JvmModifier.SYNCHRONIZED)) {
- context.report(
- ISSUE, node,
- context.getLocation(node),
- "Use of synchronized methods is not recommended",
- null
- )
+ val incident = Incident(context)
+ .fix(null)
+ .issue(ISSUE)
+ .location(context.getLocation(node))
+ .message("Use of synchronized methods is not recommended")
+ .scope(node)
+ context.report(incident)
}
}
}
diff --git a/lint-checks/src/main/java/androidx/build/lint/BanUncheckedReflection.kt b/lint-checks/src/main/java/androidx/build/lint/BanUncheckedReflection.kt
index 725f322c..6914489 100644
--- a/lint-checks/src/main/java/androidx/build/lint/BanUncheckedReflection.kt
+++ b/lint-checks/src/main/java/androidx/build/lint/BanUncheckedReflection.kt
@@ -25,6 +25,7 @@
import com.android.tools.lint.detector.api.Scope
import com.android.tools.lint.checks.VersionChecks.Companion.isWithinVersionCheckConditional
import com.android.sdklib.SdkVersionInfo.HIGHEST_KNOWN_API
+import com.android.tools.lint.detector.api.Incident
import com.android.tools.lint.detector.api.Severity
import com.android.tools.lint.detector.api.SourceCodeScanner
import com.intellij.psi.PsiMethod
@@ -53,10 +54,12 @@
if (!isWithinVersionCheckConditional(context, node, HIGHEST_KNOWN_API, false) &&
!isWithinVersionCheckConditional(context, node, 1, true)
) {
- context.report(
- ISSUE, node, context.getLocation(node),
- "Calling `Method.invoke` without an SDK check"
- )
+ val incident = Incident(context)
+ .issue(ISSUE)
+ .location(context.getLocation(node))
+ .message("Calling `Method.invoke` without an SDK check")
+ .scope(node)
+ context.report(incident)
}
}
diff --git a/lint-checks/src/main/java/androidx/build/lint/ClassVerificationFailureDetector.kt b/lint-checks/src/main/java/androidx/build/lint/ClassVerificationFailureDetector.kt
index f33aea8..6d40535 100644
--- a/lint-checks/src/main/java/androidx/build/lint/ClassVerificationFailureDetector.kt
+++ b/lint-checks/src/main/java/androidx/build/lint/ClassVerificationFailureDetector.kt
@@ -30,6 +30,7 @@
import com.android.tools.lint.detector.api.Desugaring
import com.android.tools.lint.detector.api.Detector
import com.android.tools.lint.detector.api.Implementation
+import com.android.tools.lint.detector.api.Incident
import com.android.tools.lint.detector.api.Issue
import com.android.tools.lint.detector.api.JavaContext
import com.android.tools.lint.detector.api.LintFix
@@ -474,15 +475,17 @@
// call.getContainingUClass()!! refers to the direct parent class of this method
val containingClassName = call.getContainingUClass()!!.qualifiedName.toString()
- val fix = createLintFix(method, call, api)
-
- context.report(
- ISSUE, reference, location,
- "This call references a method added in API level $api; however, the " +
+ val lintFix = createLintFix(method, call, api)
+ val incident = Incident(context)
+ .fix(lintFix)
+ .issue(ISSUE)
+ .location(location)
+ .message("This call references a method added in API level $api; however, the " +
"containing class $containingClassName is reachable from earlier API " +
- "levels and will fail run-time class verification.",
- fix,
- )
+ "levels and will fail run-time class verification.")
+ .scope(reference)
+
+ context.report(incident)
}
/**
diff --git a/lint-checks/src/main/java/androidx/build/lint/IdeaSuppressionDetector.kt b/lint-checks/src/main/java/androidx/build/lint/IdeaSuppressionDetector.kt
index 0eaeae6..e6acfb7 100644
--- a/lint-checks/src/main/java/androidx/build/lint/IdeaSuppressionDetector.kt
+++ b/lint-checks/src/main/java/androidx/build/lint/IdeaSuppressionDetector.kt
@@ -22,6 +22,7 @@
import com.android.tools.lint.detector.api.Category
import com.android.tools.lint.detector.api.Detector
import com.android.tools.lint.detector.api.Implementation
+import com.android.tools.lint.detector.api.Incident
import com.android.tools.lint.detector.api.Issue
import com.android.tools.lint.detector.api.JavaContext
import com.android.tools.lint.detector.api.Scope
@@ -127,10 +128,13 @@
val warnings = source.split(" ").drop(1).filter { JAVA_WARNINGS.contains(it) }
if (warnings.isNotEmpty()) {
val args = warnings.joinToString(", ") { "\"$it\"" }
- context.report(
- ISSUE, element, context.getNameLocation(element),
- "Uses IntelliJ-specific suppression, should use `@SuppressWarnings($args)`"
- )
+ val incident = Incident(context)
+ .issue(ISSUE)
+ .location(context.getNameLocation(element))
+ .message("Uses IntelliJ-specific suppression, should use" +
+ " `@SuppressWarnings($args)`")
+ .scope(element)
+ context.report(incident)
}
}
}
diff --git a/lint-checks/src/main/java/androidx/build/lint/MetadataTagInsideApplicationTagDetector.kt b/lint-checks/src/main/java/androidx/build/lint/MetadataTagInsideApplicationTagDetector.kt
index 94fd2e1..3a13a9f 100644
--- a/lint-checks/src/main/java/androidx/build/lint/MetadataTagInsideApplicationTagDetector.kt
+++ b/lint-checks/src/main/java/androidx/build/lint/MetadataTagInsideApplicationTagDetector.kt
@@ -21,6 +21,7 @@
import com.android.tools.lint.detector.api.Category
import com.android.tools.lint.detector.api.Detector
import com.android.tools.lint.detector.api.Implementation
+import com.android.tools.lint.detector.api.Incident
import com.android.tools.lint.detector.api.Issue
import com.android.tools.lint.detector.api.Scope
import com.android.tools.lint.detector.api.Severity
@@ -37,11 +38,13 @@
override fun visitElement(context: XmlContext, element: Element) {
if (element.parentNode.nodeName == NODE_APPLICATION) {
- context.report(
- ISSUE, element, context.getLocation(element),
- "Detected " +
- "<application>-level meta-data tag."
- )
+ val incident = Incident(context)
+ .issue(ISSUE)
+ .location(context.getLocation(element))
+ .message("Detected <application>-level meta-data tag.")
+ .scope(element)
+
+ context.report(incident)
}
}
diff --git a/lint-checks/src/main/java/androidx/build/lint/ObsoleteBuildCompatUsageDetector.kt b/lint-checks/src/main/java/androidx/build/lint/ObsoleteBuildCompatUsageDetector.kt
index 1dd909d..d3ae2de 100644
--- a/lint-checks/src/main/java/androidx/build/lint/ObsoleteBuildCompatUsageDetector.kt
+++ b/lint-checks/src/main/java/androidx/build/lint/ObsoleteBuildCompatUsageDetector.kt
@@ -21,6 +21,7 @@
import com.android.tools.lint.detector.api.Category
import com.android.tools.lint.detector.api.Detector
import com.android.tools.lint.detector.api.Implementation
+import com.android.tools.lint.detector.api.Incident
import com.android.tools.lint.detector.api.Issue
import com.android.tools.lint.detector.api.JavaContext
import com.android.tools.lint.detector.api.Scope
@@ -49,16 +50,18 @@
val target = if (node.receiver != null) node.uastParent!! else node
val apiLevel = methodsToApiLevels[node.methodName]
- val fix = fix().name("Use SDK_INT >= $apiLevel")
+ val lintFix = fix().name("Use SDK_INT >= $apiLevel")
.replace()
.text(target.asRenderString())
.with("Build.VERSION.SDK_INT >= $apiLevel")
.build()
-
- context.report(
- ISSUE, node, context.getLocation(node),
- "Using deprecated BuildCompat methods", fix
- )
+ val incident = Incident(context)
+ .fix(lintFix)
+ .issue(ISSUE)
+ .location(context.getLocation(node))
+ .message("Using deprecated BuildCompat methods")
+ .scope(node)
+ context.report(incident)
}
companion object {
diff --git a/lint-checks/src/main/java/androidx/build/lint/PrivateConstructorForUtilityClassDetector.kt b/lint-checks/src/main/java/androidx/build/lint/PrivateConstructorForUtilityClassDetector.kt
index a862563..e34e10b 100644
--- a/lint-checks/src/main/java/androidx/build/lint/PrivateConstructorForUtilityClassDetector.kt
+++ b/lint-checks/src/main/java/androidx/build/lint/PrivateConstructorForUtilityClassDetector.kt
@@ -22,6 +22,7 @@
import com.android.tools.lint.detector.api.Category
import com.android.tools.lint.detector.api.Detector
import com.android.tools.lint.detector.api.Implementation
+import com.android.tools.lint.detector.api.Incident
import com.android.tools.lint.detector.api.Issue
import com.android.tools.lint.detector.api.JavaContext
import com.android.tools.lint.detector.api.Scope
@@ -70,11 +71,12 @@
return
}
- context.report(
- ISSUE, node,
- context.getNameLocation(node),
- "Utility class is missing private constructor"
- )
+ val incident = Incident(context)
+ .issue(ISSUE)
+ .location(context.getNameLocation(node))
+ .message("Utility class is missing private constructor")
+ .scope(node)
+ context.report(incident)
}
}
diff --git a/lint-checks/src/main/java/androidx/build/lint/SampledAnnotationDetector.kt b/lint-checks/src/main/java/androidx/build/lint/SampledAnnotationDetector.kt
index a7ac02c..ab223e2 100644
--- a/lint-checks/src/main/java/androidx/build/lint/SampledAnnotationDetector.kt
+++ b/lint-checks/src/main/java/androidx/build/lint/SampledAnnotationDetector.kt
@@ -33,6 +33,7 @@
import com.android.tools.lint.detector.api.Context
import com.android.tools.lint.detector.api.Detector
import com.android.tools.lint.detector.api.Implementation
+import com.android.tools.lint.detector.api.Incident
import com.android.tools.lint.detector.api.Issue
import com.android.tools.lint.detector.api.JavaContext
import com.android.tools.lint.detector.api.LintMap
@@ -112,11 +113,11 @@
functionLocations == null -> {
locations.forEach { location ->
if (location.shouldReport()) {
- context.report(
- UNRESOLVED_SAMPLE_LINK,
- location,
- "Couldn't find a valid @Sampled function matching $link"
- )
+ val incident = Incident(context)
+ .issue(UNRESOLVED_SAMPLE_LINK)
+ .location(location)
+ .message("Couldn't find a valid @Sampled function matching $link")
+ context.report(incident)
}
}
}
@@ -125,11 +126,11 @@
functionLocations.size > 1 -> {
locations.forEach { location ->
if (location.shouldReport()) {
- context.report(
- MULTIPLE_FUNCTIONS_FOUND,
- location,
- "Found multiple functions matching $link"
- )
+ val incident = Incident(context)
+ .issue(MULTIPLE_FUNCTIONS_FOUND)
+ .location(location)
+ .message("Found multiple functions matching $link")
+ context.report(incident)
}
}
}
@@ -140,12 +141,12 @@
if (sampleLinks[link] == null) {
locations.forEach { location ->
if (location.shouldReport()) {
- context.report(
- OBSOLETE_SAMPLED_ANNOTATION,
- location,
- "$link is annotated with @$SAMPLED_ANNOTATION, but is not " +
- "linked to from a @$SAMPLE_KDOC_ANNOTATION tag."
- )
+ val incident = Incident(context)
+ .issue(OBSOLETE_SAMPLED_ANNOTATION)
+ .location(location)
+ .message("$link is annotated with @$SAMPLED_ANNOTATION, but is not " +
+ "linked to from a @$SAMPLE_KDOC_ANNOTATION tag.")
+ context.report(incident)
}
}
}
@@ -294,13 +295,13 @@
val currentPath = context.psiFile!!.virtualFile.path
if (SAMPLES_DIRECTORY !in currentPath) {
- context.report(
- INVALID_SAMPLES_LOCATION,
- node,
- context.getNameLocation(node),
- "${node.name} is annotated with @$SAMPLED_ANNOTATION" +
- ", but is not inside a project/directory named $SAMPLES_DIRECTORY."
- )
+ val incident = Incident(context)
+ .issue(INVALID_SAMPLES_LOCATION)
+ .location(context.getNameLocation(node))
+ .message("${node.name} is annotated with @$SAMPLED_ANNOTATION" +
+ ", but is not inside a project/directory named $SAMPLES_DIRECTORY.")
+ .scope(node)
+ context.report(incident)
return
}
@@ -320,11 +321,11 @@
val location = context.getNameLocation(node)
if (sampledFunctionLintMap.getLocation(fullFqName) != null) {
- context.report(
- MULTIPLE_FUNCTIONS_FOUND,
- location,
- "Found multiple functions matching $fullFqName"
- )
+ val incident = Incident(context)
+ .issue(MULTIPLE_FUNCTIONS_FOUND)
+ .location(location)
+ .message("Found multiple functions matching $fullFqName")
+ context.report(incident)
}
sampledFunctionLintMap.put(fullFqName, location)
diff --git a/lint-checks/src/main/java/androidx/build/lint/TargetApiAnnotationUsageDetector.kt b/lint-checks/src/main/java/androidx/build/lint/TargetApiAnnotationUsageDetector.kt
index 4e9fd59..7027b1e 100644
--- a/lint-checks/src/main/java/androidx/build/lint/TargetApiAnnotationUsageDetector.kt
+++ b/lint-checks/src/main/java/androidx/build/lint/TargetApiAnnotationUsageDetector.kt
@@ -23,6 +23,7 @@
import com.android.tools.lint.detector.api.Category
import com.android.tools.lint.detector.api.Detector
import com.android.tools.lint.detector.api.Implementation
+import com.android.tools.lint.detector.api.Incident
import com.android.tools.lint.detector.api.Issue
import com.android.tools.lint.detector.api.JavaContext
import com.android.tools.lint.detector.api.Scope
@@ -43,17 +44,20 @@
private inner class AnnotationChecker(val context: JavaContext) : UElementHandler() {
override fun visitAnnotation(node: UAnnotation) {
if (node.qualifiedName == "android.annotation.TargetApi") {
- context.report(
- ISSUE, node, context.getNameLocation(node),
- "Use `@RequiresApi` instead of `@TargetApi`",
- fix().name("Replace with `@RequiresApi`")
- .replace()
- .pattern("(?:android\\.annotation\\.)?TargetApi")
- .with("androidx.annotation.RequiresApi")
- .shortenNames()
- .autoFix(true, true)
- .build(),
- )
+ val lintFix = fix().name("Replace with `@RequiresApi`")
+ .replace()
+ .pattern("(?:android\\.annotation\\.)?TargetApi")
+ .with("androidx.annotation.RequiresApi")
+ .shortenNames()
+ .autoFix(true, true)
+ .build()
+ val incident = Incident(context)
+ .fix(lintFix)
+ .issue(ISSUE)
+ .location(context.getNameLocation(node))
+ .message("Use `@RequiresApi` instead of `@TargetApi`")
+ .scope(node)
+ context.report(incident)
}
}
}
diff --git a/lint-checks/src/main/java/androidx/build/lint/TestSizeAnnotationEnforcer.kt b/lint-checks/src/main/java/androidx/build/lint/TestSizeAnnotationEnforcer.kt
index 73d1b76..a6b34e6 100644
--- a/lint-checks/src/main/java/androidx/build/lint/TestSizeAnnotationEnforcer.kt
+++ b/lint-checks/src/main/java/androidx/build/lint/TestSizeAnnotationEnforcer.kt
@@ -22,6 +22,7 @@
import com.android.tools.lint.detector.api.Category
import com.android.tools.lint.detector.api.Detector
import com.android.tools.lint.detector.api.Implementation
+import com.android.tools.lint.detector.api.Incident
import com.android.tools.lint.detector.api.Issue
import com.android.tools.lint.detector.api.JavaContext
import com.android.tools.lint.detector.api.Scope
@@ -69,12 +70,13 @@
?.type?.canonicalText ?: return
if (testRunnerClassName !in ALLOWED_TEST_RUNNERS) {
- context.report(
- UNSUPPORTED_TEST_RUNNER,
- testRunner,
- context.getNameLocation(testRunner),
- "Unsupported test runner. Supported runners are: $ALLOWED_TEST_RUNNERS"
- )
+ val incident = Incident(context)
+ .issue(UNSUPPORTED_TEST_RUNNER)
+ .location(context.getNameLocation(testRunner))
+ .message("Unsupported test runner." +
+ " Supported runners are: $ALLOWED_TEST_RUNNERS")
+ .scope(testRunner)
+ context.report(incident)
return
}
@@ -95,12 +97,12 @@
// Report an issue if neither the test method nor the surrounding class have a
// valid test size annotation
if (combinedAnnotations.none { it.qualifiedName in TEST_SIZE_ANNOTATIONS }) {
- context.report(
- MISSING_TEST_SIZE_ANNOTATION,
- method,
- context.getNameLocation(method),
- "Missing test size annotation"
- )
+ val incident = Incident(context)
+ .issue(MISSING_TEST_SIZE_ANNOTATION)
+ .location(context.getNameLocation(method))
+ .message("Missing test size annotation")
+ .scope(method)
+ context.report(incident)
}
}
}
@@ -113,12 +115,12 @@
node.uAnnotations
.find { it.qualifiedName in TEST_SIZE_ANNOTATIONS }
?.let { annotation ->
- context.report(
- UNEXPECTED_TEST_SIZE_ANNOTATION,
- annotation,
- context.getNameLocation(annotation),
- "Unexpected test size annotation"
- )
+ val incident = Incident(context)
+ .issue(UNEXPECTED_TEST_SIZE_ANNOTATION)
+ .location(context.getNameLocation(annotation))
+ .message("Unexpected test size annotation")
+ .scope(annotation)
+ context.report(incident)
}
node.methods.filter {
@@ -128,12 +130,12 @@
method.uAnnotations
.find { it.qualifiedName in TEST_SIZE_ANNOTATIONS }
?.let { annotation ->
- context.report(
- UNEXPECTED_TEST_SIZE_ANNOTATION,
- annotation,
- context.getNameLocation(annotation),
- "Unexpected test size annotation"
- )
+ val incident = Incident(context)
+ .issue(UNEXPECTED_TEST_SIZE_ANNOTATION)
+ .location(context.getNameLocation(annotation))
+ .message("Unexpected test size annotation")
+ .scope(annotation)
+ context.report(incident)
}
}
}
diff --git a/lint-checks/src/test/java/androidx/build/lint/AbstractLintDetectorTest.kt b/lint-checks/src/test/java/androidx/build/lint/AbstractLintDetectorTest.kt
index da45139..0ed4dd8 100644
--- a/lint-checks/src/test/java/androidx/build/lint/AbstractLintDetectorTest.kt
+++ b/lint-checks/src/test/java/androidx/build/lint/AbstractLintDetectorTest.kt
@@ -61,6 +61,7 @@
return lint()
.projects(*projectsWithStubs)
.testModes(testModes)
+ .allowDuplicates()
.run()
}
diff --git a/lint-checks/src/test/java/androidx/build/lint/BanInappropriateExperimentalUsageTest.kt b/lint-checks/src/test/java/androidx/build/lint/BanInappropriateExperimentalUsageTest.kt
index caa51b3..59ea06a 100644
--- a/lint-checks/src/test/java/androidx/build/lint/BanInappropriateExperimentalUsageTest.kt
+++ b/lint-checks/src/test/java/androidx/build/lint/BanInappropriateExperimentalUsageTest.kt
@@ -101,6 +101,7 @@
ktSample("sample.annotation.provider.ExperimentalSampleAnnotation"),
javaSample("sample.annotation.provider.ExperimentalSampleAnnotationJava"),
javaSample("sample.annotation.provider.RequiresOptInSampleAnnotationJava"),
+ javaSample("sample.annotation.provider.RequiresOptInSampleAnnotationJavaDuplicate"),
gradle(
"""
apply plugin: 'com.android.library'
@@ -125,13 +126,22 @@
/* ktlint-disable max-line-length */
val expected = """
-../consumer/src/main/kotlin/androidx/sample/consumer/OutsideGroupExperimentalAnnotatedClass.kt:32: Error: Experimental and RequiresOptIn APIs may only be used within the same-version group where they were defined. [IllegalExperimentalApiUsage]
+../consumer/src/main/kotlin/androidx/sample/consumer/OutsideGroupExperimentalAnnotatedClass.kt:33: Error: Experimental and RequiresOptIn APIs may only be used within the same-version group where they were defined. [IllegalExperimentalApiUsage]
@ExperimentalSampleAnnotationJava
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-../consumer/src/main/kotlin/androidx/sample/consumer/OutsideGroupExperimentalAnnotatedClass.kt:37: Error: Experimental and RequiresOptIn APIs may only be used within the same-version group where they were defined. [IllegalExperimentalApiUsage]
+../consumer/src/main/kotlin/androidx/sample/consumer/OutsideGroupExperimentalAnnotatedClass.kt:38: Error: Experimental and RequiresOptIn APIs may only be used within the same-version group where they were defined. [IllegalExperimentalApiUsage]
@RequiresOptInSampleAnnotationJava
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-2 errors, 0 warnings
+../consumer/src/main/kotlin/androidx/sample/consumer/OutsideGroupExperimentalAnnotatedClass.kt:43: Error: Experimental and RequiresOptIn APIs may only be used within the same-version group where they were defined. [IllegalExperimentalApiUsage]
+ @OptIn(RequiresOptInSampleAnnotationJava::class)
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+../consumer/src/main/kotlin/androidx/sample/consumer/OutsideGroupExperimentalAnnotatedClass.kt:48: Error: Experimental and RequiresOptIn APIs may only be used within the same-version group where they were defined. [IllegalExperimentalApiUsage]
+ @OptIn(
+ ^
+../consumer/src/main/kotlin/androidx/sample/consumer/OutsideGroupExperimentalAnnotatedClass.kt:57: Error: Experimental and RequiresOptIn APIs may only be used within the same-version group where they were defined. [IllegalExperimentalApiUsage]
+ @OptIn(RequiresOptInSampleAnnotationJava::class, RequiresOptInSampleAnnotationJavaDuplicate::class)
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+5 errors, 0 warnings
""".trimIndent()
/* ktlint-enable max-line-length */
diff --git a/room/integration-tests/testapp/src/androidTest/java/androidx/room/integration/testapp/migration/MigrationTest.java b/room/integration-tests/testapp/src/androidTest/java/androidx/room/integration/testapp/migration/MigrationTest.java
index 60b5462..7927944 100644
--- a/room/integration-tests/testapp/src/androidTest/java/androidx/room/integration/testapp/migration/MigrationTest.java
+++ b/room/integration-tests/testapp/src/androidTest/java/androidx/room/integration/testapp/migration/MigrationTest.java
@@ -38,6 +38,7 @@
import androidx.room.util.TableInfo;
import androidx.room.util.ViewInfo;
import androidx.sqlite.db.SupportSQLiteDatabase;
+import androidx.sqlite.db.SupportSQLiteOpenHelper;
import androidx.sqlite.db.framework.FrameworkSQLiteOpenHelperFactory;
import androidx.test.core.app.ApplicationProvider;
import androidx.test.ext.junit.runners.AndroidJUnit4;
@@ -562,6 +563,37 @@
}
}
+ // Verifies that even with allowDataLossOnRecovery, bad migrations are propagated and the DB
+ // is not silently deleted.
+ @Test
+ public void badMigration_allowDataLossOnRecovery() throws IOException {
+ // Create DB at version 1
+ helper.createDatabase(TEST_DB, 1).close();
+
+ // Create DB at latest version, but no migrations and with allowDataLossOnRecovery, it
+ // should fail to open.
+ Context targetContext = ApplicationProvider.getApplicationContext();
+ MigrationDb db = Room.databaseBuilder(targetContext, MigrationDb.class, TEST_DB)
+ .openHelperFactory(configuration -> {
+ SupportSQLiteOpenHelper.Configuration config =
+ SupportSQLiteOpenHelper.Configuration.builder(targetContext)
+ .name(configuration.name)
+ .callback(configuration.callback)
+ .allowDataLossOnRecovery(true)
+ .build();
+ return new FrameworkSQLiteOpenHelperFactory().create(config);
+ })
+ .build();
+ try {
+ db.getOpenHelper().getWritableDatabase();
+ Assert.fail("Expected a missing migration exception");
+ } catch (IllegalStateException ex) {
+ // Verifies exception is not wrapped
+ Truth.assertThat(ex).hasMessageThat()
+ .containsMatch("A migration from \\d+ to \\d+ was required but not found.");
+ }
+ }
+
private void testFailure(int startVersion, int endVersion) throws IOException {
final SupportSQLiteDatabase db = helper.createDatabase(TEST_DB, startVersion);
db.close();
diff --git a/settings.gradle b/settings.gradle
index 877d050..9bf36a2 100644
--- a/settings.gradle
+++ b/settings.gradle
@@ -456,6 +456,7 @@
includeProject(":core:core-animation-testing", "core/core-animation-testing", [BuildType.MAIN])
includeProject(":core:core-appdigest", "core/core-appdigest", [BuildType.MAIN])
includeProject(":core:core-google-shortcuts", "core/core-google-shortcuts", [BuildType.MAIN])
+includeProject(":core:core-i18n", "core/core-i18n", [BuildType.MAIN])
includeProject(":core:core-ktx", "core/core-ktx", [BuildType.MAIN])
includeProject(":core:core-performance", "core/core-performance", [BuildType.MAIN])
includeProject(":core:core-performance:core-performance-samples", "core/core-performance/samples", [BuildType.MAIN])
diff --git a/slice/slice-core/build.gradle b/slice/slice-core/build.gradle
index c53b92d..4c0c448 100644
--- a/slice/slice-core/build.gradle
+++ b/slice/slice-core/build.gradle
@@ -23,7 +23,7 @@
dependencies {
api("androidx.annotation:annotation:1.1.0")
- implementation(project(":appcompat:appcompat"))
+ implementation("androidx.appcompat:appcompat:1.4.0")
implementation("androidx.collection:collection:1.1.0")
androidTestImplementation(libs.testExtJunit)
diff --git a/slice/slice-view/build.gradle b/slice/slice-view/build.gradle
index 8fe1d23..e51b6ac 100644
--- a/slice/slice-view/build.gradle
+++ b/slice/slice-view/build.gradle
@@ -23,7 +23,7 @@
dependencies {
implementation(project(":slice:slice-core"))
- implementation(project(":appcompat:appcompat"))
+ implementation("androidx.appcompat:appcompat:1.4.0")
implementation("androidx.recyclerview:recyclerview:1.2.0-beta01")
implementation("androidx.collection:collection:1.1.0")
api("androidx.lifecycle:lifecycle-livedata-core:2.0.0")
diff --git a/sqlite/sqlite-framework/build.gradle b/sqlite/sqlite-framework/build.gradle
index 8e92837..9dfb2cf 100644
--- a/sqlite/sqlite-framework/build.gradle
+++ b/sqlite/sqlite-framework/build.gradle
@@ -19,11 +19,20 @@
plugins {
id("AndroidXPlugin")
id("com.android.library")
+ id("org.jetbrains.kotlin.android")
}
dependencies {
api("androidx.annotation:annotation:1.2.0")
api(project(":sqlite:sqlite"))
+ androidTestImplementation(libs.kotlinStdlib)
+ androidTestImplementation(libs.testExtJunit)
+ androidTestImplementation(libs.testCore)
+ androidTestImplementation(libs.testRunner) {
+ exclude module: "support-annotations"
+ exclude module: "hamcrest-core"
+ }
+ androidTestImplementation(libs.truth)
}
androidx {
diff --git a/sqlite/sqlite-framework/src/androidTest/java/androidx/sqlite/db/framework/OpenHelperRecoveryTest.kt b/sqlite/sqlite-framework/src/androidTest/java/androidx/sqlite/db/framework/OpenHelperRecoveryTest.kt
new file mode 100644
index 0000000..2ff6161
--- /dev/null
+++ b/sqlite/sqlite-framework/src/androidTest/java/androidx/sqlite/db/framework/OpenHelperRecoveryTest.kt
@@ -0,0 +1,248 @@
+/*
+ * Copyright 2022 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.sqlite.db.framework
+
+import android.content.Context
+import android.database.sqlite.SQLiteException
+import androidx.sqlite.db.SupportSQLiteDatabase
+import androidx.sqlite.db.SupportSQLiteOpenHelper
+import androidx.test.core.app.ApplicationProvider
+import androidx.test.filters.LargeTest
+import com.google.common.truth.Truth.assertThat
+import org.junit.Assert.fail
+import org.junit.Before
+import org.junit.Test
+
+@LargeTest
+class OpenHelperRecoveryTest {
+
+ private val dbName = "test.db"
+ private val context: Context = ApplicationProvider.getApplicationContext()
+
+ @Before
+ fun setup() {
+ context.deleteDatabase(dbName)
+ }
+
+ @Test
+ fun writeOver() {
+ val openHelper = FrameworkSQLiteOpenHelper(context, dbName, EmptyCallback(), false, false)
+ openHelper.writableDatabase.use { db ->
+ db.execSQL("CREATE TABLE Foo (id INTEGER PRIMARY KEY)")
+ db.query("SELECT * FROM sqlite_master WHERE name = 'Foo'").use {
+ assertThat(it.count).isEqualTo(1)
+ }
+ }
+
+ val dbFile = context.getDatabasePath(dbName)
+ assertThat(dbFile.exists()).isTrue()
+ assertThat(dbFile.length()).isGreaterThan(0)
+ dbFile.writeText("malas vibra")
+
+ try {
+ openHelper.writableDatabase
+ fail("Database should have failed to open.")
+ } catch (ex: SQLiteException) {
+ // Expected
+ }
+ }
+
+ @Test
+ fun writeOver_allowDataLossOnRecovery() {
+ val openHelper = FrameworkSQLiteOpenHelper(context, dbName, EmptyCallback(), false, true)
+ openHelper.writableDatabase.use { db ->
+ db.execSQL("CREATE TABLE Foo (id INTEGER PRIMARY KEY)")
+ db.query("SELECT * FROM sqlite_master WHERE name = 'Foo'").use {
+ assertThat(it.count).isEqualTo(1)
+ }
+ }
+
+ val dbFile = context.getDatabasePath(dbName)
+ assertThat(dbFile.exists()).isTrue()
+ assertThat(dbFile.length()).isGreaterThan(0)
+ dbFile.writeText("malas vibra")
+
+ openHelper.writableDatabase.use { db ->
+ db.query("SELECT * FROM sqlite_master WHERE name = 'Foo'").use {
+ assertThat(it.count).isEqualTo(0)
+ }
+ }
+ }
+
+ @Test
+ fun allowDataLossOnRecovery_onCreateError() {
+ var createAttempts = 0
+ val badCallback = object : SupportSQLiteOpenHelper.Callback(1) {
+ override fun onCreate(db: SupportSQLiteDatabase) {
+ if (createAttempts++ < 2) {
+ throw RuntimeException("Not an SQLiteException")
+ }
+ }
+ override fun onUpgrade(db: SupportSQLiteDatabase, oldVersion: Int, newVersion: Int) {}
+ }
+ val openHelper = FrameworkSQLiteOpenHelper(context, dbName, badCallback, false, true)
+ try {
+ openHelper.writableDatabase
+ fail("Database should have failed to open.")
+ } catch (ex: RuntimeException) {
+ // Expected
+ assertThat(ex.message).contains("Not an SQLiteException")
+ }
+ assertThat(createAttempts).isEqualTo(2)
+ }
+
+ @Test
+ fun allowDataLossOnRecovery_onUpgradeError() {
+ // Create DB at version 1, open and close it
+ FrameworkSQLiteOpenHelper(context, dbName, EmptyCallback(1), false, true).let {
+ it.writableDatabase.close()
+ }
+
+ // A callback to open DB at version 2, it has a bad migration.
+ val badCallback = object : SupportSQLiteOpenHelper.Callback(2) {
+ override fun onCreate(db: SupportSQLiteDatabase) {}
+ override fun onUpgrade(db: SupportSQLiteDatabase, oldVersion: Int, newVersion: Int) {
+ db.execSQL("SELECT * FROM bad_table")
+ }
+ }
+ val openHelper = FrameworkSQLiteOpenHelper(context, dbName, badCallback, false, true)
+ try {
+ openHelper.writableDatabase
+ fail("Database should have failed to open.")
+ } catch (ex: SQLiteException) {
+ // Expected
+ assertThat(ex.message).contains("no such table: bad_table")
+ }
+ }
+
+ @Test
+ fun allowDataLossOnRecovery_onOpenNonSQLiteError() {
+ var openAttempts = 0
+ val badCallback = object : SupportSQLiteOpenHelper.Callback(1) {
+ override fun onCreate(db: SupportSQLiteDatabase) {}
+ override fun onUpgrade(db: SupportSQLiteDatabase, oldVersion: Int, newVersion: Int) {}
+ override fun onOpen(db: SupportSQLiteDatabase) {
+ if (openAttempts++ < 2) {
+ throw RuntimeException("Not an SQLiteException")
+ }
+ }
+ }
+ val openHelper = FrameworkSQLiteOpenHelper(context, dbName, badCallback, false, true)
+ try {
+ openHelper.writableDatabase
+ fail("Database should have failed to open.")
+ } catch (ex: RuntimeException) {
+ // Expected
+ assertThat(ex.message).contains("Not an SQLiteException")
+ }
+ assertThat(openAttempts).isEqualTo(2)
+ }
+
+ @Test
+ fun allowDataLossOnRecovery_onOpenSQLiteError_intermediate() {
+ FrameworkSQLiteOpenHelper(context, dbName, EmptyCallback(), false, false)
+ .writableDatabase.use { db ->
+ db.execSQL("CREATE TABLE Foo (id INTEGER PRIMARY KEY)")
+ }
+
+ var openAttempts = 0
+ val badCallback = object : SupportSQLiteOpenHelper.Callback(1) {
+ override fun onCreate(db: SupportSQLiteDatabase) {}
+ override fun onUpgrade(db: SupportSQLiteDatabase, oldVersion: Int, newVersion: Int) {}
+ override fun onOpen(db: SupportSQLiteDatabase) {
+ if (openAttempts++ < 1) {
+ db.execSQL("SELECT * FROM bad_table")
+ }
+ }
+ }
+ // With only 1 onOpen error, the database is opened without being deleted, simulates an
+ // intermediate error.
+ val openHelper = FrameworkSQLiteOpenHelper(context, dbName, badCallback, false, true)
+ openHelper.writableDatabase.use { db ->
+ db.query("SELECT * FROM sqlite_master WHERE name = 'Foo'").use {
+ assertThat(it.count).isEqualTo(1)
+ }
+ }
+ assertThat(openAttempts).isEqualTo(2)
+ }
+
+ @Test
+ fun allowDataLossOnRecovery_onOpenSQLiteError_recoverable() {
+ FrameworkSQLiteOpenHelper(context, dbName, EmptyCallback(), false, false)
+ .writableDatabase.use { db ->
+ db.execSQL("CREATE TABLE Foo (id INTEGER PRIMARY KEY)")
+ }
+
+ var openAttempts = 0
+ val badCallback = object : SupportSQLiteOpenHelper.Callback(1) {
+ override fun onCreate(db: SupportSQLiteDatabase) {}
+ override fun onUpgrade(db: SupportSQLiteDatabase, oldVersion: Int, newVersion: Int) {}
+ override fun onOpen(db: SupportSQLiteDatabase) {
+ if (openAttempts++ < 2) {
+ db.execSQL("SELECT * FROM bad_table")
+ }
+ }
+ }
+ // With 2 onOpen error, the database is opened by deleting it, simulating a recoverable
+ // error.
+ val openHelper = FrameworkSQLiteOpenHelper(context, dbName, badCallback, false, true)
+ openHelper.writableDatabase.use { db ->
+ db.query("SELECT * FROM sqlite_master WHERE name = 'Foo'").use {
+ assertThat(it.count).isEqualTo(0)
+ }
+ }
+ assertThat(openAttempts).isEqualTo(3)
+ }
+
+ @Test
+ fun allowDataLossOnRecovery_onOpenSQLiteError_permanent() {
+ var openAttempts = 0
+ val badCallback = object : SupportSQLiteOpenHelper.Callback(1) {
+ override fun onCreate(db: SupportSQLiteDatabase) {}
+ override fun onUpgrade(db: SupportSQLiteDatabase, oldVersion: Int, newVersion: Int) {}
+ override fun onOpen(db: SupportSQLiteDatabase) {
+ openAttempts++
+ db.execSQL("SELECT * FROM bad_table")
+ }
+ }
+ // Consistent onOpen error, might be a user bug or an actual SQLite permanent error,
+ // nothing we can do here, expect failure
+ val openHelper = FrameworkSQLiteOpenHelper(context, dbName, badCallback, false, true)
+ try {
+ openHelper.writableDatabase
+ fail("Database should have failed to open.")
+ } catch (ex: SQLiteException) {
+ // Expected
+ assertThat(ex.message).contains("no such table: bad_table")
+ }
+ assertThat(openAttempts).isEqualTo(3)
+ }
+
+ class EmptyCallback(version: Int = 1) : SupportSQLiteOpenHelper.Callback(version) {
+ override fun onCreate(db: SupportSQLiteDatabase) {
+ }
+
+ override fun onUpgrade(db: SupportSQLiteDatabase, oldVersion: Int, newVersion: Int) {
+ }
+
+ override fun onOpen(db: SupportSQLiteDatabase) {
+ }
+
+ override fun onCorruption(db: SupportSQLiteDatabase) {
+ }
+ }
+}
\ No newline at end of file
diff --git a/sqlite/sqlite-framework/src/main/java/androidx/sqlite/db/framework/FrameworkSQLiteOpenHelper.java b/sqlite/sqlite-framework/src/main/java/androidx/sqlite/db/framework/FrameworkSQLiteOpenHelper.java
index b3f6a12..7a76b70 100644
--- a/sqlite/sqlite-framework/src/main/java/androidx/sqlite/db/framework/FrameworkSQLiteOpenHelper.java
+++ b/sqlite/sqlite-framework/src/main/java/androidx/sqlite/db/framework/FrameworkSQLiteOpenHelper.java
@@ -19,24 +19,31 @@
import android.content.Context;
import android.database.DatabaseErrorHandler;
import android.database.sqlite.SQLiteDatabase;
+import android.database.sqlite.SQLiteException;
import android.database.sqlite.SQLiteOpenHelper;
import android.os.Build;
+import android.util.Log;
+import androidx.annotation.NonNull;
import androidx.annotation.RequiresApi;
import androidx.sqlite.db.SupportSQLiteCompat;
import androidx.sqlite.db.SupportSQLiteDatabase;
import androidx.sqlite.db.SupportSQLiteOpenHelper;
import androidx.sqlite.util.ProcessLock;
+import androidx.sqlite.util.SneakyThrow;
import java.io.File;
import java.util.UUID;
class FrameworkSQLiteOpenHelper implements SupportSQLiteOpenHelper {
+ private static final String TAG = "SupportSQLite";
+
private final Context mContext;
private final String mName;
private final Callback mCallback;
private final boolean mUseNoBackupDirectory;
+ private final boolean mAllowDataLossOnRecovery;
private final Object mLock;
// Delegate is created lazily
@@ -55,10 +62,20 @@
String name,
Callback callback,
boolean useNoBackupDirectory) {
+ this(context, name, callback, useNoBackupDirectory, false);
+ }
+
+ FrameworkSQLiteOpenHelper(
+ Context context,
+ String name,
+ Callback callback,
+ boolean useNoBackupDirectory,
+ boolean allowDataLossOnRecovery) {
mContext = context;
mName = name;
mCallback = callback;
mUseNoBackupDirectory = useNoBackupDirectory;
+ mAllowDataLossOnRecovery = allowDataLossOnRecovery;
mLock = new Object();
}
@@ -80,9 +97,11 @@
SupportSQLiteCompat.Api21Impl.getNoBackupFilesDir(mContext),
mName
);
- mDelegate = new OpenHelper(mContext, file.getAbsolutePath(), dbRef, mCallback);
+ mDelegate = new OpenHelper(mContext, file.getAbsolutePath(), dbRef, mCallback,
+ mAllowDataLossOnRecovery);
} else {
- mDelegate = new OpenHelper(mContext, mName, dbRef, mCallback);
+ mDelegate = new OpenHelper(mContext, mName, dbRef, mCallback,
+ mAllowDataLossOnRecovery);
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
SupportSQLiteCompat.Api16Impl.setWriteAheadLoggingEnabled(mDelegate,
@@ -111,12 +130,12 @@
@Override
public SupportSQLiteDatabase getWritableDatabase() {
- return getDelegate().getWritableSupportDatabase();
+ return getDelegate().getSupportDatabase(true);
}
@Override
public SupportSQLiteDatabase getReadableDatabase() {
- return getDelegate().getReadableSupportDatabase();
+ return getDelegate().getSupportDatabase(false);
}
@Override
@@ -131,7 +150,9 @@
* constructor.
*/
final FrameworkSQLiteDatabase[] mDbRef;
+ final Context mContext;
final Callback mCallback;
+ final boolean mAllowDataLossOnRecovery;
// see b/78359448
private boolean mMigrated;
// see b/193182592
@@ -139,7 +160,7 @@
private boolean mOpened;
OpenHelper(Context context, String name, final FrameworkSQLiteDatabase[] dbRef,
- final Callback callback) {
+ final Callback callback, boolean allowDataLossOnRecovery) {
super(context, name, null, callback.version,
new DatabaseErrorHandler() {
@Override
@@ -147,21 +168,23 @@
callback.onCorruption(getWrappedDb(dbRef, dbObj));
}
});
+ mContext = context;
mCallback = callback;
mDbRef = dbRef;
+ mAllowDataLossOnRecovery = allowDataLossOnRecovery;
mLock = new ProcessLock(name == null ? UUID.randomUUID().toString() : name,
context.getCacheDir(), false);
}
- SupportSQLiteDatabase getWritableSupportDatabase() {
+ SupportSQLiteDatabase getSupportDatabase(boolean writable) {
try {
mLock.lock(!mOpened && getDatabaseName() != null);
mMigrated = false;
- SQLiteDatabase db = super.getWritableDatabase();
+ final SQLiteDatabase db = innerGetDatabase(writable);
if (mMigrated) {
// there might be a connection w/ stale structure, we should re-open.
close();
- return getWritableSupportDatabase();
+ return getSupportDatabase(writable);
}
return getWrappedDb(db);
} finally {
@@ -169,19 +192,87 @@
}
}
- SupportSQLiteDatabase getReadableSupportDatabase() {
- try {
- mLock.lock(!mOpened && getDatabaseName() != null);
- mMigrated = false;
- SQLiteDatabase db = super.getReadableDatabase();
- if (mMigrated) {
- // there might be a connection w/ stale structure, we should re-open.
- close();
- return getReadableSupportDatabase();
+ private SQLiteDatabase innerGetDatabase(boolean writable) {
+ String name = getDatabaseName();
+ if (name != null) {
+ File databaseFile = mContext.getDatabasePath(name);
+ File parentFile = databaseFile.getParentFile();
+ if (parentFile != null) {
+ parentFile.mkdirs();
+ if (!parentFile.isDirectory()) {
+ Log.w(TAG, "Invalid database parent file, not a directory: " + parentFile);
+ }
}
- return getWrappedDb(db);
- } finally {
- mLock.unlock();
+ }
+
+ try {
+ return getWritableOrReadableDatabase(writable);
+ } catch (Throwable t) {
+ // No good, just try again...
+ super.close();
+ }
+
+ try {
+ // Wait before trying to open the DB, ideally enough to account for some slow I/O.
+ // Similar to android_database_SQLiteConnection's BUSY_TIMEOUT_MS but not as much.
+ Thread.sleep(500);
+ } catch (InterruptedException e) {
+ // Ignore, and continue
+ }
+
+ final Throwable openRetryError;
+ try {
+ return getWritableOrReadableDatabase(writable);
+ } catch (Throwable t) {
+ super.close();
+ openRetryError = t;
+ }
+ if (openRetryError instanceof CallbackException) {
+ // Callback error (onCreate, onUpgrade, onOpen, etc), possibly user error.
+ final CallbackException callbackException = (CallbackException) openRetryError;
+ final Throwable cause = callbackException.getCause();
+ switch (callbackException.getCallbackName()) {
+ case ON_CONFIGURE:
+ case ON_CREATE:
+ case ON_UPGRADE:
+ case ON_DOWNGRADE:
+ SneakyThrow.reThrow(cause);
+ break;
+ case ON_OPEN:
+ default:
+ break;
+ }
+ // If callback exception is not an SQLiteException, then more certainly it is not
+ // recoverable.
+ if (!(cause instanceof SQLiteException)) {
+ SneakyThrow.reThrow(cause);
+ }
+ } else if (openRetryError instanceof SQLiteException) {
+ // Ideally we are looking for SQLiteCantOpenDatabaseException and similar, but
+ // corruption can manifest in others forms.
+ if (name == null || !mAllowDataLossOnRecovery) {
+ SneakyThrow.reThrow(openRetryError);
+ }
+ } else {
+ SneakyThrow.reThrow(openRetryError);
+ }
+
+ // Delete the database and try one last time. (mAllowDataLossOnRecovery == true)
+ mContext.deleteDatabase(name);
+ try {
+ return getWritableOrReadableDatabase(writable);
+ } catch (CallbackException ex) {
+ // Unwrap our exception to avoid disruption with other try-catch in the call stack.
+ SneakyThrow.reThrow(ex.getCause());
+ return null; // Unreachable code, but compiler doesn't know it.
+ }
+ }
+
+ private SQLiteDatabase getWritableOrReadableDatabase(boolean writable) {
+ if (writable) {
+ return super.getWritableDatabase();
+ } else {
+ return super.getReadableDatabase();
}
}
@@ -191,31 +282,51 @@
@Override
public void onCreate(SQLiteDatabase sqLiteDatabase) {
- mCallback.onCreate(getWrappedDb(sqLiteDatabase));
+ try {
+ mCallback.onCreate(getWrappedDb(sqLiteDatabase));
+ } catch (Throwable t) {
+ throw new CallbackException(CallbackName.ON_CREATE, t);
+ }
}
@Override
public void onUpgrade(SQLiteDatabase sqLiteDatabase, int oldVersion, int newVersion) {
mMigrated = true;
- mCallback.onUpgrade(getWrappedDb(sqLiteDatabase), oldVersion, newVersion);
+ try {
+ mCallback.onUpgrade(getWrappedDb(sqLiteDatabase), oldVersion, newVersion);
+ } catch (Throwable t) {
+ throw new CallbackException(CallbackName.ON_UPGRADE, t);
+ }
}
@Override
public void onConfigure(SQLiteDatabase db) {
- mCallback.onConfigure(getWrappedDb(db));
+ try {
+ mCallback.onConfigure(getWrappedDb(db));
+ } catch (Throwable t) {
+ throw new CallbackException(CallbackName.ON_CONFIGURE, t);
+ }
}
@Override
public void onDowngrade(SQLiteDatabase db, int oldVersion, int newVersion) {
mMigrated = true;
- mCallback.onDowngrade(getWrappedDb(db), oldVersion, newVersion);
+ try {
+ mCallback.onDowngrade(getWrappedDb(db), oldVersion, newVersion);
+ } catch (Throwable t) {
+ throw new CallbackException(CallbackName.ON_DOWNGRADE, t);
+ }
}
@Override
public void onOpen(SQLiteDatabase db) {
if (!mMigrated) {
// if we've migrated, we'll re-open the db so we should not call the callback.
- mCallback.onOpen(getWrappedDb(db));
+ try {
+ mCallback.onOpen(getWrappedDb(db));
+ } catch (Throwable t) {
+ throw new CallbackException(CallbackName.ON_OPEN, t);
+ }
}
mOpened = true;
}
@@ -241,5 +352,36 @@
}
return refHolder[0];
}
+
+ private static final class CallbackException extends RuntimeException {
+
+ private final CallbackName mCallbackName;
+ private final Throwable mCause;
+
+ CallbackException(CallbackName callbackName, Throwable cause) {
+ super(cause);
+ mCallbackName = callbackName;
+ mCause = cause;
+ }
+
+ public CallbackName getCallbackName() {
+ return mCallbackName;
+ }
+
+ @NonNull
+ @Override
+ @SuppressWarnings("UnsynchronizedOverridesSynchronized") // Not needed, cause is final
+ public Throwable getCause() {
+ return mCause;
+ }
+ }
+
+ enum CallbackName {
+ ON_CONFIGURE,
+ ON_CREATE,
+ ON_UPGRADE,
+ ON_DOWNGRADE,
+ ON_OPEN
+ }
}
}
diff --git a/sqlite/sqlite-framework/src/main/java/androidx/sqlite/db/framework/FrameworkSQLiteOpenHelperFactory.java b/sqlite/sqlite-framework/src/main/java/androidx/sqlite/db/framework/FrameworkSQLiteOpenHelperFactory.java
index 2f55202..34a0ce0 100644
--- a/sqlite/sqlite-framework/src/main/java/androidx/sqlite/db/framework/FrameworkSQLiteOpenHelperFactory.java
+++ b/sqlite/sqlite-framework/src/main/java/androidx/sqlite/db/framework/FrameworkSQLiteOpenHelperFactory.java
@@ -33,6 +33,7 @@
configuration.context,
configuration.name,
configuration.callback,
- configuration.useNoBackupDirectory);
+ configuration.useNoBackupDirectory,
+ configuration.allowDataLossOnRecovery);
}
}
diff --git a/sqlite/sqlite-framework/src/main/java/androidx/sqlite/util/SneakyThrow.java b/sqlite/sqlite-framework/src/main/java/androidx/sqlite/util/SneakyThrow.java
new file mode 100644
index 0000000..e1309b4
--- /dev/null
+++ b/sqlite/sqlite-framework/src/main/java/androidx/sqlite/util/SneakyThrow.java
@@ -0,0 +1,47 @@
+/*
+ * 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.sqlite.util;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.RestrictTo;
+
+/**
+ * Java 8 Sneaky Throw technique.
+ *
+ * @hide
+ */
+@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
+public class SneakyThrow {
+
+ /**
+ * Re-throws a throwable as if it was a runtime exception without wrapping it.
+ *
+ * @param t the throwable to re-throw.
+ */
+ public static void reThrow(@NonNull Throwable t) {
+ sneakyThrow(t);
+ }
+
+ @SuppressWarnings("unchecked")
+ private static <E extends Throwable> void sneakyThrow(@NonNull Throwable t) throws E {
+ throw (E) t;
+ }
+
+ private SneakyThrow() {
+
+ }
+}
diff --git a/sqlite/sqlite/api/api_lint.ignore b/sqlite/sqlite/api/api_lint.ignore
index 4e9a7e3..3d31715 100644
--- a/sqlite/sqlite/api/api_lint.ignore
+++ b/sqlite/sqlite/api/api_lint.ignore
@@ -41,6 +41,8 @@
Builder methods names should use setFoo() / addFoo() / clearFoo() style: method androidx.sqlite.db.SupportSQLiteOpenHelper.Configuration.Builder.name(String)
BuilderSetStyle: androidx.sqlite.db.SupportSQLiteOpenHelper.Configuration.Builder#noBackupDirectory(boolean):
Builder methods names should use setFoo() / addFoo() / clearFoo() style: method androidx.sqlite.db.SupportSQLiteOpenHelper.Configuration.Builder.noBackupDirectory(boolean)
+BuilderSetStyle: androidx.sqlite.db.SupportSQLiteOpenHelper.Configuration.Builder#allowDataLossOnRecovery(boolean):
+ Builder methods names should use setFoo() / addFoo() / clearFoo() style: method androidx.sqlite.db.SupportSQLiteOpenHelper.Configuration.Builder.allowDataLossOnRecovery(boolean)
BuilderSetStyle: androidx.sqlite.db.SupportSQLiteQueryBuilder#builder(String):
Builder methods names should use setFoo() / addFoo() / clearFoo() style: method androidx.sqlite.db.SupportSQLiteQueryBuilder.builder(String)
BuilderSetStyle: androidx.sqlite.db.SupportSQLiteQueryBuilder#columns(String[]):
diff --git a/sqlite/sqlite/api/current.txt b/sqlite/sqlite/api/current.txt
index dbdea61..927cb1b 100644
--- a/sqlite/sqlite/api/current.txt
+++ b/sqlite/sqlite/api/current.txt
@@ -74,6 +74,7 @@
public static class SupportSQLiteOpenHelper.Configuration {
method public static androidx.sqlite.db.SupportSQLiteOpenHelper.Configuration.Builder builder(android.content.Context);
+ field public final boolean allowDataLossOnRecovery;
field public final androidx.sqlite.db.SupportSQLiteOpenHelper.Callback callback;
field public final android.content.Context context;
field public final String? name;
@@ -81,6 +82,7 @@
}
public static class SupportSQLiteOpenHelper.Configuration.Builder {
+ method public androidx.sqlite.db.SupportSQLiteOpenHelper.Configuration.Builder allowDataLossOnRecovery(boolean);
method public androidx.sqlite.db.SupportSQLiteOpenHelper.Configuration build();
method public androidx.sqlite.db.SupportSQLiteOpenHelper.Configuration.Builder callback(androidx.sqlite.db.SupportSQLiteOpenHelper.Callback);
method public androidx.sqlite.db.SupportSQLiteOpenHelper.Configuration.Builder name(String?);
diff --git a/sqlite/sqlite/api/public_plus_experimental_current.txt b/sqlite/sqlite/api/public_plus_experimental_current.txt
index dbdea61..927cb1b 100644
--- a/sqlite/sqlite/api/public_plus_experimental_current.txt
+++ b/sqlite/sqlite/api/public_plus_experimental_current.txt
@@ -74,6 +74,7 @@
public static class SupportSQLiteOpenHelper.Configuration {
method public static androidx.sqlite.db.SupportSQLiteOpenHelper.Configuration.Builder builder(android.content.Context);
+ field public final boolean allowDataLossOnRecovery;
field public final androidx.sqlite.db.SupportSQLiteOpenHelper.Callback callback;
field public final android.content.Context context;
field public final String? name;
@@ -81,6 +82,7 @@
}
public static class SupportSQLiteOpenHelper.Configuration.Builder {
+ method public androidx.sqlite.db.SupportSQLiteOpenHelper.Configuration.Builder allowDataLossOnRecovery(boolean);
method public androidx.sqlite.db.SupportSQLiteOpenHelper.Configuration build();
method public androidx.sqlite.db.SupportSQLiteOpenHelper.Configuration.Builder callback(androidx.sqlite.db.SupportSQLiteOpenHelper.Callback);
method public androidx.sqlite.db.SupportSQLiteOpenHelper.Configuration.Builder name(String?);
diff --git a/sqlite/sqlite/api/restricted_current.txt b/sqlite/sqlite/api/restricted_current.txt
index dbdea61..927cb1b 100644
--- a/sqlite/sqlite/api/restricted_current.txt
+++ b/sqlite/sqlite/api/restricted_current.txt
@@ -74,6 +74,7 @@
public static class SupportSQLiteOpenHelper.Configuration {
method public static androidx.sqlite.db.SupportSQLiteOpenHelper.Configuration.Builder builder(android.content.Context);
+ field public final boolean allowDataLossOnRecovery;
field public final androidx.sqlite.db.SupportSQLiteOpenHelper.Callback callback;
field public final android.content.Context context;
field public final String? name;
@@ -81,6 +82,7 @@
}
public static class SupportSQLiteOpenHelper.Configuration.Builder {
+ method public androidx.sqlite.db.SupportSQLiteOpenHelper.Configuration.Builder allowDataLossOnRecovery(boolean);
method public androidx.sqlite.db.SupportSQLiteOpenHelper.Configuration build();
method public androidx.sqlite.db.SupportSQLiteOpenHelper.Configuration.Builder callback(androidx.sqlite.db.SupportSQLiteOpenHelper.Callback);
method public androidx.sqlite.db.SupportSQLiteOpenHelper.Configuration.Builder name(String?);
diff --git a/sqlite/sqlite/src/main/java/androidx/sqlite/db/SupportSQLiteOpenHelper.java b/sqlite/sqlite/src/main/java/androidx/sqlite/db/SupportSQLiteOpenHelper.java
index 0123606..55d37ea 100644
--- a/sqlite/sqlite/src/main/java/androidx/sqlite/db/SupportSQLiteOpenHelper.java
+++ b/sqlite/sqlite/src/main/java/androidx/sqlite/db/SupportSQLiteOpenHelper.java
@@ -322,6 +322,11 @@
* If {@code true} the database will be stored in the no-backup directory.
*/
public final boolean useNoBackupDirectory;
+ /**
+ * If {@code true} the database will be delete and its data loss in the case that it
+ * cannot be opened.
+ */
+ public final boolean allowDataLossOnRecovery;
Configuration(
@NonNull Context context,
@@ -335,10 +340,20 @@
@Nullable String name,
@NonNull Callback callback,
boolean useNoBackupDirectory) {
+ this(context, name, callback, useNoBackupDirectory, false);
+ }
+
+ Configuration(
+ @NonNull Context context,
+ @Nullable String name,
+ @NonNull Callback callback,
+ boolean useNoBackupDirectory,
+ boolean allowDataLossOnRecovery) {
this.context = context;
this.name = name;
this.callback = callback;
this.useNoBackupDirectory = useNoBackupDirectory;
+ this.allowDataLossOnRecovery = allowDataLossOnRecovery;
}
/**
@@ -359,6 +374,7 @@
String mName;
SupportSQLiteOpenHelper.Callback mCallback;
boolean mUseNoBackupDirectory;
+ boolean mAllowDataLossOnRecovery;
/**
* <p>
@@ -386,7 +402,8 @@
"Must set a non-null database name to a configuration that uses the "
+ "no backup directory.");
}
- return new Configuration(mContext, mName, mCallback, mUseNoBackupDirectory);
+ return new Configuration(mContext, mName, mCallback, mUseNoBackupDirectory,
+ mAllowDataLossOnRecovery);
}
Builder(@NonNull Context context) {
@@ -424,6 +441,19 @@
mUseNoBackupDirectory = useNoBackupDirectory;
return this;
}
+
+ /**
+ * Sets whether to delete and recreate the database file in situations when the
+ * database file cannot be opened, thus allowing for its data to be lost.
+ * @param allowDataLossOnRecovery If {@code true} the database file might be recreated
+ * in the case that it cannot be opened.
+ * @return this
+ */
+ @NonNull
+ public Builder allowDataLossOnRecovery(boolean allowDataLossOnRecovery) {
+ mAllowDataLossOnRecovery = allowDataLossOnRecovery;
+ return this;
+ }
}
}
diff --git a/wear/compose/compose-material/src/commonMain/kotlin/androidx/wear/compose/material/Picker.kt b/wear/compose/compose-material/src/commonMain/kotlin/androidx/wear/compose/material/Picker.kt
index cdfdca0..38df852 100644
--- a/wear/compose/compose-material/src/commonMain/kotlin/androidx/wear/compose/material/Picker.kt
+++ b/wear/compose/compose-material/src/commonMain/kotlin/androidx/wear/compose/material/Picker.kt
@@ -40,6 +40,7 @@
* A scrollable list of items to pick from. By default, items will be repeated
* "infinitely" in both directions, unless [PickerState#repeatItems] is specified as false.
*
+ * Example of a simple picker to select one of five options:
* @sample androidx.wear.compose.material.samples.SimplePicker
*
* @param state The state of the component
diff --git a/wear/compose/integration-tests/demos/src/main/java/androidx/wear/compose/integration/demos/MaterialDemos.kt b/wear/compose/integration-tests/demos/src/main/java/androidx/wear/compose/integration/demos/MaterialDemos.kt
index 11ad138..91a5fc1 100644
--- a/wear/compose/integration-tests/demos/src/main/java/androidx/wear/compose/integration/demos/MaterialDemos.kt
+++ b/wear/compose/integration-tests/demos/src/main/java/androidx/wear/compose/integration/demos/MaterialDemos.kt
@@ -93,6 +93,23 @@
"Material",
listOf(
DemoCategory(
+ "Picker",
+ listOf(
+ ComposableDemo("Time HH:MM:SS") {
+ TimePickerWithHoursMinutesSeconds()
+ },
+ ComposableDemo("Time 12 Hour") {
+ TimePickerWith12HourClock()
+ },
+ ComposableDemo("Simple Picker") {
+ SimplePicker()
+ },
+ ComposableDemo("Change Selected Option Picker") {
+ OptionChangePicker()
+ },
+ )
+ ),
+ DemoCategory(
"Slider",
listOf(
DemoCategory(
@@ -375,19 +392,6 @@
)
),
ComposableDemo("Curved Text") { CurvedTextDemo() },
- DemoCategory(
- "Picker",
- listOf(
- ComposableDemo("Simple Picker") {
- SimplePicker()
- },
- ComposableDemo("Change Selected Option Picker") {
- OptionChangePicker()
- },
- ComposableDemo("Time Picker") {
- PickerTimeDemo()
- }
- )
- )
+
),
)
diff --git a/wear/compose/integration-tests/demos/src/main/java/androidx/wear/compose/integration/demos/PickerDemo.kt b/wear/compose/integration-tests/demos/src/main/java/androidx/wear/compose/integration/demos/PickerDemo.kt
index 56af8be..b29cd08 100644
--- a/wear/compose/integration-tests/demos/src/main/java/androidx/wear/compose/integration/demos/PickerDemo.kt
+++ b/wear/compose/integration-tests/demos/src/main/java/androidx/wear/compose/integration/demos/PickerDemo.kt
@@ -1,5 +1,5 @@
/*
- * Copyright 2021 The Android Open Source Project
+ * Copyright 2022 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.
@@ -16,79 +16,305 @@
package androidx.wear.compose.integration.demos
-import androidx.compose.foundation.background
+import android.view.MotionEvent
+import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.BoxScope
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxSize
-import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.height
+import androidx.compose.foundation.layout.offset
import androidx.compose.foundation.layout.size
+import androidx.compose.foundation.layout.width
import androidx.compose.foundation.layout.wrapContentSize
import androidx.compose.runtime.Composable
-import androidx.compose.runtime.LaunchedEffect
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
+import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
+import androidx.compose.ui.ExperimentalComposeUiApi
import androidx.compose.ui.Modifier
-import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.input.pointer.pointerInteropFilter
+import androidx.compose.ui.res.painterResource
+import androidx.compose.ui.text.TextStyle
+import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
+import androidx.wear.compose.material.Button
+import androidx.wear.compose.material.ChipDefaults
+import androidx.wear.compose.material.CompactChip
+import androidx.wear.compose.material.Icon
import androidx.wear.compose.material.MaterialTheme
import androidx.wear.compose.material.Picker
+import androidx.wear.compose.material.PickerDefaults
+import androidx.wear.compose.material.PickerScope
+import androidx.wear.compose.material.PickerState
+import androidx.wear.compose.material.ScalingParams
import androidx.wear.compose.material.Text
import androidx.wear.compose.material.rememberPickerState
+@OptIn(ExperimentalComposeUiApi::class)
@Composable
-fun PickerTimeDemo() {
- // The option initially selected on the Picker can be passed to rememberPickerState()
- val hourState = rememberPickerState(numberOfOptions = 24, initiallySelectedOption = 6)
- val minuteState = rememberPickerState(numberOfOptions = 60)
- LaunchedEffect(true) {
- // This is possible, but not desirable, since the observed state.selectedOption may take a
- // few frames to update to this value.
- minuteState.scrollToOption(15)
- }
+fun TimePickerWithHoursMinutesSeconds() {
+ var selectedColumn by remember { mutableStateOf(0) }
+ val textStyle = MaterialTheme.typography.display2
+ val optionColor = MaterialTheme.colors.secondary
Box(modifier = Modifier.fillMaxSize()) {
- Row(
- modifier = Modifier.align(Alignment.Center),
- verticalAlignment = Alignment.CenterVertically
+ Column(
+ verticalArrangement = Arrangement.Center,
+ horizontalAlignment = Alignment.CenterHorizontally
) {
- Picker(
- state = hourState,
- modifier = Modifier
- .size(50.dp, 80.dp)
- .background(color = Color.Black),
- separation = (-10).dp
- ) {
- Text(
- modifier = Modifier.wrapContentSize(),
- text = it.toString(),
- style = MaterialTheme.typography.display3
- )
- }
- Spacer(modifier = Modifier.size(10.dp))
- Picker(
- state = minuteState,
- modifier = Modifier
- .size(50.dp, 80.dp)
- .background(color = Color.Black),
- separation = (-10).dp
- ) {
- Text(
- modifier = Modifier.wrapContentSize(),
- text = "%02d".format(it),
- style = MaterialTheme.typography.display3
- )
- }
- }
-
- Box(
- modifier = Modifier
- .align(Alignment.TopCenter)
- .padding(top = 20.dp)
- ) {
+ Spacer(Modifier.height(16.dp))
Text(
- "${hourState.selectedOption} : " +
- "%02d".format(minuteState.selectedOption),
- style = MaterialTheme.typography.title3.copy(color = Color.White)
+ text = when (selectedColumn) {
+ 0 -> "Hour"
+ 1 -> "Minute"
+ else -> "Second"
+ },
+ color = optionColor,
+ style = MaterialTheme.typography.button,
+ maxLines = 1,
+ )
+ val weightsToCenterVertically = 0.5f
+ Spacer(Modifier.fillMaxWidth().weight(weightsToCenterVertically))
+ Row(
+ modifier = Modifier.fillMaxWidth(),
+ verticalAlignment = Alignment.CenterVertically,
+ horizontalArrangement = Arrangement.SpaceEvenly,
+ ) {
+ val selectablePickerModifier = Modifier.size(54.dp, 120.dp)
+ val separation = (-10).dp
+ Spacer(Modifier.width(8.dp))
+ SelectablePicker(
+ selected = selectedColumn == 0,
+ state = rememberPickerState(numberOfOptions = 24, initiallySelectedOption = 6),
+ modifier = selectablePickerModifier,
+ separation = separation,
+ ) { hour: Int, selected: Boolean ->
+ TimePiece(
+ selected = selected,
+ onSelected = { selectedColumn = 0 },
+ text = "%02d".format(hour),
+ style = textStyle,
+ )
+ }
+ Separator(4.dp)
+ SelectablePicker(
+ selected = selectedColumn == 1,
+ state = rememberPickerState(numberOfOptions = 60),
+ modifier = selectablePickerModifier,
+ separation = separation,
+ ) { minute: Int, selected: Boolean ->
+ TimePiece(
+ selected = selected,
+ onSelected = { selectedColumn = 1 },
+ text = "%02d".format(minute),
+ style = textStyle,
+ )
+ }
+ Separator(4.dp)
+ SelectablePicker(
+ selected = selectedColumn == 2,
+ state = rememberPickerState(numberOfOptions = 60),
+ modifier = selectablePickerModifier,
+ separation = separation,
+ ) { second: Int, selected: Boolean ->
+ TimePiece(
+ selected = selected,
+ onSelected = { selectedColumn = 2 },
+ text = "%02d".format(second),
+ style = textStyle,
+ )
+ }
+ Spacer(Modifier.width(8.dp))
+ }
+ Spacer(Modifier.fillMaxWidth().weight(weightsToCenterVertically))
+ Button(onClick = {}) {
+ Icon(
+ painter = painterResource(id = R.drawable.ic_check_24px),
+ contentDescription = "check",
+ modifier = Modifier.size(24.dp).wrapContentSize(align = Alignment.Center),
+ )
+ }
+ Spacer(Modifier.height(12.dp))
+ }
+ }
+}
+
+@OptIn(ExperimentalComposeUiApi::class)
+@Composable
+fun TimePickerWith12HourClock() {
+ var morning by remember { mutableStateOf(true) }
+ var selectedColumn by remember { mutableStateOf(0) }
+ Column(
+ modifier = Modifier.fillMaxSize(),
+ verticalArrangement = Arrangement.Center,
+ horizontalAlignment = Alignment.CenterHorizontally
+ ) {
+ Spacer(Modifier.height(16.dp))
+ CompactChip(
+ onClick = { morning = !morning },
+ modifier = Modifier.size(width = 50.dp, height = 24.dp),
+ label = {
+ Row(
+ modifier = Modifier.fillMaxWidth(),
+ horizontalArrangement = Arrangement.Center) {
+ Text(
+ text = if (morning) "AM" else "PM",
+ color = MaterialTheme.colors.onPrimary,
+ style = MaterialTheme.typography.button,
+ )
+ }
+ },
+ colors = ChipDefaults.chipColors(backgroundColor = MaterialTheme.colors.secondary),
+ contentPadding = PaddingValues(vertical = 0.dp),
+ )
+ Spacer(Modifier.fillMaxWidth().weight(0.5f))
+ Row(
+ modifier = Modifier.fillMaxWidth(),
+ verticalAlignment = Alignment.CenterVertically,
+ horizontalArrangement = Arrangement.Center,
+ ) {
+ Spacer(Modifier.width(8.dp))
+ SelectablePicker(
+ selected = selectedColumn == 0,
+ state = rememberPickerState(numberOfOptions = 12, initiallySelectedOption = 6),
+ modifier = Modifier.size(64.dp, 120.dp),
+ separation = (-10).dp,
+ label = { LabelText("Hour") }
+ ) { hour: Int, selected: Boolean ->
+ TimePiece(
+ selected = selected,
+ onSelected = { selectedColumn = 0 },
+ text = "%2d".format(hour + 1),
+ )
+ }
+ Separator(8.dp)
+ SelectablePicker(
+ selected = selectedColumn == 1,
+ state = rememberPickerState(numberOfOptions = 60),
+ modifier = Modifier.size(64.dp, 120.dp),
+ separation = (-10).dp,
+ label = { LabelText("Minute") }
+ ) { minute: Int, selected: Boolean ->
+ TimePiece(
+ selected = selected,
+ onSelected = { selectedColumn = 1 },
+ text = "%02d".format(minute),
+ )
+ }
+ Spacer(Modifier.width(8.dp))
+ }
+ Spacer(Modifier.fillMaxWidth().weight(0.5f))
+ Button(onClick = {}) {
+ Icon(
+ painter = painterResource(id = R.drawable.ic_check_24px),
+ contentDescription = "check",
+ modifier = Modifier.size(24.dp).wrapContentSize(align = Alignment.Center),
+ )
+ }
+ Spacer(Modifier.height(12.dp))
+ }
+}
+
+/**
+ * [SelectablePicker] builds on the functionality of [Picker], displaying a [Picker] when selected
+ * and providing a slot for content (typically [Text]) otherwise.
+ *
+ * @param selected Determines whether the [SelectablePicker] is selected
+ * (in which case it shows a Picker).
+ * @param state The state of the component.
+ * @param modifier Modifier to be applied to the Picker. Typically provides size for the underlying
+ * [Picker].
+ * @param label A slot for providing a label, displayed above the [SelectablePicker]
+ * when unselected.
+ * @param scalingParams the parameters to configure the scaling and transparency effects for the
+ * component. See [ScalingParams]
+ * @param separation the amount of separation in [Dp] between items. Can be negative, which can be
+ * useful for Text if it has plenty of whitespace.
+ * @param option a block which describes the content. Inside this block you can reference
+ * [PickerScope.selectedOption] and other properties in [PickerScope]. The Int parameter determines
+ * the option index and the Boolean parameter determines whether the Pickable is selected (typically
+ * this is used to change the appearance of the content, e.g. by setting a highlight color).
+ */
+@OptIn(ExperimentalComposeUiApi::class)
+@Composable
+private fun SelectablePicker(
+ selected: Boolean,
+ state: PickerState,
+ modifier: Modifier = Modifier,
+ label: @Composable (BoxScope.() -> Unit)? = null,
+ scalingParams: ScalingParams = PickerDefaults.scalingParams(),
+ separation: Dp = 0.dp,
+ option: @Composable BoxScope.(Int, Boolean) -> Unit
+) {
+ Box(modifier = modifier) {
+ if (selected) {
+ Picker(
+ state = state,
+ modifier = Modifier.fillMaxSize(),
+ separation = separation,
+ scalingParams = scalingParams,
+ ) {
+ option(it, true)
+ }
+ } else {
+ if (label != null) {
+ label()
+ }
+ option(
+ state.selectedOption,
+ false
)
}
}
-}
\ No newline at end of file
+}
+
+@OptIn(ExperimentalComposeUiApi::class)
+@Composable
+private fun BoxScope.TimePiece(
+ selected: Boolean,
+ onSelected: () -> Unit,
+ text: String,
+ style: TextStyle = MaterialTheme.typography.display1,
+) {
+ val modifier = Modifier.align(Alignment.Center).wrapContentSize()
+ Text(
+ text = text,
+ style = style,
+ color =
+ if (selected) MaterialTheme.colors.secondary
+ else MaterialTheme.colors.onBackground,
+ modifier =
+ if (selected) modifier
+ else modifier.pointerInteropFilter {
+ if (it.action == MotionEvent.ACTION_DOWN) onSelected()
+ true
+ },
+ )
+}
+
+@Composable
+private fun BoxScope.LabelText(text: String) {
+ Text(
+ text = text,
+ style = MaterialTheme.typography.button,
+ color = MaterialTheme.colors.onBackground,
+ modifier = Modifier.align(Alignment.TopCenter).offset(y = 12.dp)
+ )
+}
+
+@Composable
+private fun Separator(width: Dp) {
+ Spacer(Modifier.width(width))
+ Text(
+ text = ":",
+ style = MaterialTheme.typography.display2,
+ color = MaterialTheme.colors.onBackground
+ )
+ Spacer(Modifier.width(width))
+}
diff --git a/wear/compose/integration-tests/macrobenchmark-target/src/main/java/androidx/wear/compose/integration/macrobenchmark/target/BaselineActivity.kt b/wear/compose/integration-tests/macrobenchmark-target/src/main/java/androidx/wear/compose/integration/macrobenchmark/target/BaselineActivity.kt
index a00755a..832a6740 100644
--- a/wear/compose/integration-tests/macrobenchmark-target/src/main/java/androidx/wear/compose/integration/macrobenchmark/target/BaselineActivity.kt
+++ b/wear/compose/integration-tests/macrobenchmark-target/src/main/java/androidx/wear/compose/integration/macrobenchmark/target/BaselineActivity.kt
@@ -164,7 +164,6 @@
}
composable(PROGRESS_INDICATOR_INDETERMINATE) {
Column(
- modifier = Modifier.fillMaxSize(),
verticalArrangement = Arrangement.Center,
horizontalAlignment = Alignment.CenterHorizontally
) {